MyBatis SqlSessionFactory 批量执行实战

在 MyBatis 中,批量操作是处理高并发数据写入的核心场景之一。通过 SqlSessionFactory 配置批处理执行器(ExecutorType.BATCH),可以显著提升数据库操作的效率。本文将结合 Spring 框架,深入解析如何高效配置和使用 MyBatis 的批量执行功能,并提供性能优化策略。

一、SqlSessionFactory 基础配置

1. ​​添加依赖​​

在 pom.xml 中引入 MyBatis-Spring 相关依赖:

复制
<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.2.0version>
dependency>
<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.2.6version>
dependency>

注:若使用非 Spring Boot 项目,需手动配置 SqlSessionFactoryBean 和 SqlSessionTemplate。

2. ​​配置数据源​​

在 application.yml(Spring Boot)或 Spring XML 配置文件中定义数据源:

复制
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

3. ​​配置 SqlSessionFactory​​

通过 SqlSessionFactoryBean 创建 SqlSessionFactory,并绑定数据源:

@Configuration
public class MyBatisConfig {
    @Autowired
    private DataSource dataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        // 指定 MyBatis 配置文件路径(可选)
        factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        return factoryBean.getObject();
    }
}

XML 配置示例:

复制
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
bean>

二、执行器配置

1.1 执行器类型选择

MyBatis 默认使用 SIMPLE 执行器,每次操作生成独立 SQL 语句。而 BATCH 执行器通过复用预编译语句(PreparedStatement)和合并参数,减少数据库交互次数,适用于批量插入/更新场景。

  • ​​全局配置示例(mybatis-config.xml)​​:
<configuration>
  <settings>
    <setting name="defaultExecutorType" value="BATCH"/> 
  settings>
configuration>
  • ​​单次会话配置(Spring 整合)​​:
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
try {
    UserMapper mapper = batchSession.getMapper(UserMapper.class);
    // 执行批量操作
    batchSession.commit();
} finally {
    batchSession.close();
}

三、Spring 框架深度整合

3.1 配置批处理 SqlSessionTemplate(Java Config)

@Configuration
@MapperScan(basePackages = "com.example.mapper") // 自动扫描 Mapper 接口
public class MyBatisBatchConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) 
            throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        
        // 配置全局执行器为 BATCH
        org.apache.ibatis.session.Configuration configuration = 
            new org.apache.ibatis.session.Configuration();
        configuration.setDefaultExecutorType(ExecutorType.BATCH);
        factoryBean.setConfiguration(configuration);

        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        // 显式指定批处理模式
        return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
    }
}

3.2 DAO 层注入与使用

@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private SqlSessionTemplate batchSqlSession;

    @Override
    @Transactional
    public void batchInsert(List<User> users) {
        UserMapper mapper = batchSqlSession.getMapper(UserMapper.class);
        mapper.insertBatch(users);
    }
}

四、配置特性解析

1. 执行器类型控制​​

  • 通过 SqlSessionFactoryBean.setConfiguration() 设置全局执行器
  • 或通过 SqlSessionTemplate 构造函数指定(优先级更高)

2. ​​事务管理集成​​

  • 结合 @Transactional 注解确保批量操作原子性
  • 推荐在 Service 层方法添加注解,而非 DAO 层

3. 性能优化配置​​

# application.yml
mybatis:
  configuration:
    default-executor-type: BATCH
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      id-type: auto

五、最佳实践建议

1. ​​组合使用配置​​

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

2. 连接池优化​​

spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5

3. ​​批量大小控制​​


int batchSize = 500;
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    UserMapper mapper = batchSession.getMapper(UserMapper.class);
    for (int i = 0; i < users.size(); i++) {
        mapper.insert(users.get(i));
        if (i % batchSize == 0) {
            batchSession.commit();
            batchSession.clearCache(); // 清理缓存
        }
    }
    batchSession.commit();
} finally {
    batchSession.close();
}

通过 Java Config 方式可实现更细粒度的配置控制,结合 Spring Boot 的自动配置特性,既能保持配置简洁性,又能满足高性能批量操作需求。建议生产环境采用混合配置模式:基础配置使用 YAML,特殊场景通过 Java Config 覆盖。

六、实战案例分享–同步儿童信息

	@Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public int syncChildDataListNew(PeoChildScreeningSyncDto dto) throws Exception {
        int result = 0;
        int errorCount = 0;
        PeoChildScreening queryParams = new PeoChildScreening();

        // 1. 分页参数
        int pageSize = Optional.ofNullable(dto.getPageSize()).orElse(1000);
        int total = peoChildScreeningMapper.selectPeoChildListCount(queryParams);
        int totalPages = (total + pageSize - 1) / pageSize;

        // 2. 批处理参数
        final int BATCH_SIZE = 5;

        try (SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
            PeoChildScreeningMapper batchMapper = batchSession.getMapper(PeoChildScreeningMapper.class);

            for (int pageNum = 1; pageNum <= totalPages; pageNum++) {
                PageHelper.startPage(pageNum, pageSize);
                List<PeoChildScreening> list = peoChildScreeningMapper.selectPeoChildList(queryParams);
                PageHelper.clearPage();

                if (CollectionUtils.isEmpty(list)) continue;
                // 3. 批量处理逻辑
                try {
                    // 3.1 批量查询旧数据(按childPeoBaseId批量查询)
                    String[] childIdsArray = list.stream()
                            .map(PeoChildScreening::getChildPeoBaseId)
                            .toArray(String[]::new);
                    PeoChildScreening query = new PeoChildScreening();
                    List<PeoChildScreening> oldList = batchMapper.selectPeoChildScreeningBatch(childIdsArray);// 改造后的批量查询方法

                    // 3.2 批量校验与处理
                    Map<String, PeoChildScreening> oldMap = oldList.stream()
                            .collect(Collectors.toMap(PeoChildScreening::getChildPeoBaseId, Function.identity()));

                    List<PeoChildScreening> updateList = new ArrayList<>();
                    List<PeoChildScreening> insertList = new ArrayList<>();

                    for (PeoChildScreening item : list) {
                        String childId = item.getChildPeoBaseId();
                        PeoChildScreening oldItem = oldMap.get(childId);
                        //儿童孩次查询
                        item.setGb302(String.valueOf(getChildOrder(item)));
                        // 校验逻辑(批量处理)
                        Map<String, Object> validateInfo = PeoChildScreeningUtil.validateInfo(item);
                        //校验信息
                        if(validateInfo.get("isValid") != null && (boolean) validateInfo.get("isValid")){
                            item.setSystemScreeningStatus("1");//通过
                            item.setSystemScreeningReason("通过");
                        }else {
                            item.setSystemScreeningStatus("2");//不通过
                            item.setSystemScreeningReason(validateInfo.get("errorMessage").toString());
                        }
                        item.setSystemScreeningTime(DateUtils.getTime());//系统核查时间
                        // 更新或插入判断
                        if (oldItem != null && StringUtils.isNotEmpty(oldItem.getId())) {
                            BeanCopyUtils.springCopyIgnoreNull(item, oldItem);
                            updateList.add(oldItem);
                        } else {
                            item.setId(IdUtils.simpleUUID());
                            item.setAuditStatus("2");//默认未审核
                            insertList.add(item);
                        }
                    }

                    // 3.3 批量提交
                    if (!updateList.isEmpty()) {
                        // 执行批量更新
                        for (PeoChildScreening peoChildScreening : updateList) {
                            batchMapper.updatePeoChildScreening(peoChildScreening);
                        }
                    }
                    if (!insertList.isEmpty()) {
                        // 执行批量插入
                        for (PeoChildScreening peoChildScreening : insertList) {
                            batchMapper.insertPeoChildScreening(peoChildScreening);
                        }
                    }

                    batchSession.commit();
                    result += list.size();
                } catch (Exception e) {
                    batchSession.rollback();
                    errorCount += list.size();
                    log.error("第 {} 页处理失败", pageNum, e);
                }
            }
        }
        log.info("同步完成 - 成功: {}, 失败: {}", result, errorCount);
        return result;
    }

你可能感兴趣的:(springboot,mybatis)