mybatis在springboot项目中的批次操作

在 Spring 中,使用 @Transactional 注解管理事务,可以确保多个数据库操作在同一个事务中进行。在 @Transactional 注解的方法中,如果要对两个表执行批量插入操作,并使用 MyBatis 的 BATCH 执行器类型的 SqlSession ,可以通过自定义获取 SqlSession 来实现。

关键点:

  1. @Transactional 注解:用于保证两个批量插入操作在同一个事务中执行,且只会在事务提交时才将数据持久化到数据库。
  2. BATCH 执行器类型的 SqlSession :通过指定 ExecutorType.BATCH 来执行批量操作。
  3. 手动触发批处理 :在合适的时机调用 sqlSession.flushStatements() 来执行批量操作,并在事务最后提交。

示例代码

1. Service 层代码:使用 @TransactionalBATCH 模式
java 复制代码
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class BatchInsertService {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private OrderMapper orderMapper;

    private static final int BATCH_SIZE = 500;

    // 事务管理注解,确保方法内的操作在同一个事务中
    @Transactional
    public void batchInsertUsersAndOrders(List<User> users, List<Order> orders) {
        // 获取批处理模式的 SqlSession
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            UserMapper userBatchMapper = sqlSession.getMapper(UserMapper.class);
            OrderMapper orderBatchMapper = sqlSession.getMapper(OrderMapper.class);

            // 批量插入用户数据
            for (int i = 0; i < users.size(); i++) {
                userBatchMapper.insertUser(users.get(i));
                // 达到批次大小时,手动触发批处理执行
                if (i % BATCH_SIZE == 0 && i != 0) {
                    sqlSession.flushStatements();  // 执行批量插入
                }
            }

            // 批量插入订单数据
            for (int i = 0; i < orders.size(); i++) {
                orderBatchMapper.insertOrder(orders.get(i));
                // 达到批次大小时,手动触发批处理执行
                if (i % BATCH_SIZE == 0 && i != 0) {
                    sqlSession.flushStatements();  // 执行批量插入
                }
            }

            // 手动提交所有批量操作
            sqlSession.flushStatements();  // 执行剩余的批处理
            sqlSession.commit();  // 提交事务
        }
    }
}
2. Mapper 层代码:定义单条插入方法

UserMapperOrderMapper 都是 MyBatis 的 Mapper 接口,定义批量插入的单条 SQL 操作。

  • UserMapper.java
java 复制代码
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    
    // 单条插入用户数据
    @Insert("INSERT INTO user (name, age) VALUES (#{name}, #{age})")
    void insertUser(User user);
}
  • OrderMapper.java
java 复制代码
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper {
    
    // 单条插入订单数据
    @Insert("INSERT INTO orders (order_id, user_id, product_id) VALUES (#{orderId}, #{userId}, #{productId})")
    void insertOrder(Order order);
}

关键点解释

  1. @Transactional 注解

    • 确保整个方法 batchInsertUsersAndOrders 在同一个事务中执行。如果中途出现异常,所有的插入操作都会回滚,不会部分提交。
  2. ExecutorType.BATCH

    • 使用 ExecutorType.BATCH,MyBatis 将 SQL 语句批量缓存起来,直到 flushStatements() 被调用时才会实际执行。
  3. flushStatements()

    • 手动触发批处理操作。每当批次达到一定大小(如 500 条记录),或者在所有数据插入完毕时,调用该方法来执行批量插入。
  4. sqlSession.commit()

    • SqlSession 中的批处理完成后,显式调用 commit() 提交事务。如果不调用 commit(),即使 flushStatements() 执行,数据也不会最终提交到数据库。
  5. 批次大小

    • 批处理操作是按一定大小来执行的。这里设置每 500 条数据执行一次批处理,防止批处理缓存过大导致内存占用问题。

总结

  • 事务控制@Transactional 确保了两个批量操作都在同一个事务中进行。
  • 批量插入 :通过 ExecutorType.BATCH 执行批量插入,使用 sqlSession.flushStatements()commit() 来控制批处理的执行和事务提交。
  • 性能优化:批处理可以减少数据库交互次数,显著提升批量插入性能。

通过这种方式,你可以在 @Transactional 方法中对两个表执行批量插入,并在一个事务中提交所有操作,从而保证数据的一致性和批处理性能。

batchInsertUsersAndOrders 方法拆成两个不同的 SqlSession 来执行批量插入,是一种控制粒度更细的方式,可以独立地对 UserOrder 执行插入操作,甚至可以选择独立提交每个操作的事务。如果仍然需要它们在同一个事务中执行,只需确保使用 @Transactional 注解将这两个 SqlSession 操作包含在同一个事务上下文中。

下面是拆分成两个 SqlSession 来执行的示例:

拆分后的代码结构

java 复制代码
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class BatchInsertService {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private OrderMapper orderMapper;

    private static final int BATCH_SIZE = 500;

    // 事务管理注解,确保两个批量操作在同一个事务中
    @Transactional
    public void batchInsertUsersAndOrders(List<User> users, List<Order> orders) {
        batchInsertUsers(users);  // 使用一个 SqlSession 执行用户批量插入
        batchInsertOrders(orders); // 使用另一个 SqlSession 执行订单批量插入
    }

    // 使用一个 SqlSession 批量插入用户
    private void batchInsertUsers(List<User> users) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            UserMapper userBatchMapper = sqlSession.getMapper(UserMapper.class);
            
            for (int i = 0; i < users.size(); i++) {
                userBatchMapper.insertUser(users.get(i));
                if (i % BATCH_SIZE == 0 && i != 0) {
                    sqlSession.flushStatements();  // 批量提交
                }
            }
            sqlSession.flushStatements();  // 提交剩余的批处理
            sqlSession.commit();  // 提交事务
        }
    }

    // 使用另一个 SqlSession 批量插入订单
    private void batchInsertOrders(List<Order> orders) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            OrderMapper orderBatchMapper = sqlSession.getMapper(OrderMapper.class);
            
            for (int i = 0; i < orders.size(); i++) {
                orderBatchMapper.insertOrder(orders.get(i));
                if (i % BATCH_SIZE == 0 && i != 0) {
                    sqlSession.flushStatements();  // 批量提交
                }
            }
            sqlSession.flushStatements();  // 提交剩余的批处理
            sqlSession.commit();  // 提交事务
        }
    }
}

关键点解释:

  1. 两个独立的 SqlSession

    • batchInsertUsersbatchInsertOrders 使用了各自独立的 SqlSession。这意味着每个 SqlSession 负责管理自己的数据库连接和批处理操作。
  2. 同一个事务

    • 尽管这两个方法使用了不同的 SqlSession,因为在方法上使用了 @Transactional 注解,Spring 会确保这两个 SqlSession 操作仍然在同一个事务中执行。如果一个插入失败,整个事务会回滚,两个表的操作都不会被提交。
  3. flushStatements()commit()

    • 每个 SqlSession 负责自己批处理的提交。flushStatements() 确保在批量插入操作中定期执行提交,防止内存溢出或SQL语句过长。
    • commit() 方法会提交该 SqlSession 中的事务,但由于使用了 @Transactional,整个事务是在 Spring 的控制下提交的,commit() 相当于在每个 SqlSession 中确认所有插入操作。
  4. 为什么拆分 SqlSession

    • 这种拆分可以让你更灵活地控制不同表的批量操作,特别是当批量操作非常大或者有复杂的批处理逻辑时,拆分成多个 SqlSession 能更好地控制数据库资源。

拆分成两个 SqlSession 的优缺点

优点:
  • 更灵活的控制:你可以分别控制不同表的插入操作。如果某个表的批量插入出问题,仍然可以选择回滚或处理其他表的数据。
  • 内存控制 :对于大批量数据,拆分可以减少内存压力。每个 SqlSession 的批处理缓存更容易管理,减少溢出风险。
  • 可扩展性:当表的数据结构和逻辑独立时,拆分可以让代码更清晰,也更容易扩展。
缺点:
  • 更多的复杂性 :每个 SqlSession 都需要手动管理其批量操作和事务提交。如果业务逻辑要求非常强的原子性(所有操作要么成功要么失败),需要额外小心确保事务的一致性。

总结

通过拆分成两个 SqlSession,你可以对批量插入操作进行更灵活的控制,同时仍然可以利用 @Transactional 来确保两个表的操作在同一个事务中执行。

sqlsession.flushStatements()的作用

仅执行 sqlSession.commit() 而不执行 sqlSession.flushStatements() 是可以的,但是这样做有一些潜在的影响,需要根据具体情况来判断是否合适。

1. sqlSession.commit() vs sqlSession.flushStatements()

  • sqlSession.flushStatements():这是 MyBatis 提供的一个方法,用于在批量操作时将缓存中的 SQL 语句立即发送到数据库执行。它不会提交事务,而是执行批量缓存的 SQL 语句。你可以在批处理过程中多次调用它,确保 SQL 不会一次性缓存太多。

  • sqlSession.commit() :这个方法不仅会执行所有的批处理操作,还会提交事务,意味着你在这个 SqlSession 中所有的数据库更改都会被提交并永久生效。如果调用 commit() 时尚未调用 flushStatements(),MyBatis 会自动在 commit() 时先执行 flushStatements(),然后提交事务。

2. 是否可以只使用 commit() 而不调用 flushStatements()

是可以的,因为在调用 commit() 的时候,MyBatis 会自动执行批处理操作 ,即它会自动调用 flushStatements() 将所有缓存的 SQL 语句发送到数据库。

关键点:
  • 自动触发 flushStatements() :当你调用 sqlSession.commit() 时,MyBatis 会自动先将缓存的 SQL 语句通过 flushStatements() 发送到数据库,然后再提交事务。因此,如果你直接调用 commit(),它实际上已经隐式地执行了 flushStatements()

  • 事务提交commit() 不仅会将缓存的 SQL 发送到数据库,还会提交事务。如果你的整个批处理只需要在最后一次执行所有 SQL 并提交事务,那调用 commit() 就足够了。

3. 何时需要显式调用 flushStatements()

你显式调用 flushStatements() 的场景通常是在你需要对批处理的执行进行更细粒度的控制时,比如以下情况:

  • 大批量操作 :如果你正在批量插入大量数据,例如几千甚至上万条记录,可能希望在处理一部分数据后就将它们发送到数据库,而不是一次性缓存所有的 SQL。这可以防止内存占用过大或 SQL 语句缓存溢出的问题。显式调用 flushStatements() 允许你在批次内分阶段执行 SQL。

  • 逐步执行批次:在处理大数据集时,可以按批次将 SQL 语句发送到数据库,而不必等到整个操作结束再统一提交。

示例:

java 复制代码
for (int i = 0; i < users.size(); i++) {
    userMapper.insertUser(users.get(i));
    if (i % BATCH_SIZE == 0 && i != 0) {
        sqlSession.flushStatements();  // 手动触发批处理
    }
}
sqlSession.commit();  // 提交事务

在这个例子中,flushStatements() 用于分批执行插入操作,确保不会一次性缓存过多的 SQL 语句,而最后的 commit() 用于提交整个事务。

4. 仅使用 commit() 的场景

如果数据量相对较小(例如几百条记录以内),缓存的 SQL 语句不会占用太多资源,那么你可以直接调用 sqlSession.commit(),而不必担心显式调用 flushStatements()。MyBatis 会在提交时自动处理缓存的 SQL。

示例:

java 复制代码
for (User user : users) {
    userMapper.insertUser(user);
}
sqlSession.commit();  // 提交事务并自动执行批量操作

在这个例子中,你不需要手动调用 flushStatements(),因为 commit() 会自动执行缓存的 SQL 并提交事务。

5. 总结

  • 可以不调用 flushStatements() :如果你只在最后调用 sqlSession.commit(),MyBatis 会自动在提交前执行所有缓存的 SQL 语句,相当于隐式调用了 flushStatements()

  • 什么时候需要调用 flushStatements() :如果你处理的数据量非常大,或者需要在事务提交之前分批执行部分 SQL,显式调用 flushStatements() 是必要的。

  • 直接调用 commit() 的适用场景 :对于小规模批处理或不需要分阶段执行 SQL 的操作,直接调用 sqlSession.commit() 即可。

相关推荐
除了菜一无所有!9 分钟前
基于SpringBoot技术的教务管理
java·spring boot·后端
lexusv8ls600h1 小时前
微服务设计模式 - 断路器模式 (Circuit Breaker Pattern)
java·微服务·设计模式
逸狼1 小时前
【JavaEE初阶】网络原理(2)
java·网络·java-ee
甲柒1 小时前
12-Docker发布微服务
java·docker·微服务
一只特立独行的猪6111 小时前
Java面试题——微服务篇
java·开发语言·微服务
浅念同学1 小时前
JavaEE-多线程上
java·java-ee
liuyang-neu3 小时前
力扣 简单 70.爬楼梯
java·算法·leetcode
不惑_3 小时前
Redis与MySQL双写一致性的缓存模式
redis·mysql·缓存
喵手3 小时前
Java 与 Oracle 数据泵实操:数据导入导出的全方位指南
java·开发语言·oracle
是丝豆呀4 小时前
清理pip和conda缓存
缓存·conda·pip