mybatis批量保存工具类实用

一、代码

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.function.Consumer;


/**
 * 批处理工具
 * @author hulei
 */
public class BatchInsertUtil {
    private static final Logger logger = LoggerFactory.getLogger(BatchInsertUtil.class);
    private static final SqlSessionFactory sqlSessionFactory = SpringUtils.getBean(SqlSessionFactory.class);
    private static final int DEFAULT_BATCH_SIZE = 1000;

    private static int validateAndReturnBatchSize(Integer handleCount) {
        if (handleCount == null || handleCount <= 0) {
            throw new IllegalArgumentException("批处理条数必须大于0");
        }
        return handleCount;
    }

    public static <T, U> int batchUpdateOrInsert(List<T> data, Class<U> mapperClass, Consumer<Pair<T, U>> consumer) {
        int handleCount = validateAndReturnBatchSize(DEFAULT_BATCH_SIZE);
        int processedCount = 0;
        SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        try {
            U mapper = batchSqlSession.getMapper(mapperClass);
            for (T element : data) {
                consumer.accept(new Pair<>(element, mapper));
                processedCount++;
                if (processedCount % handleCount == 0) {
                    batchSqlSession.flushStatements();
                }
            }
            batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive());
        } catch (Exception e) {
            logger.error("批处理发生异常:{}", e.getMessage(), e);
            batchSqlSession.rollback();
            throw new RuntimeException("批量处理失败", e);
        } finally {
            batchSqlSession.close();
        }
        return processedCount;
    }

    public static void jdbcTemplateBatchUpdateOrInsert(JdbcTemplate jdbcTemplate, String updateOrInsertSQL, List<Object[]> batchArgs) {
        int handleCount = validateAndReturnBatchSize(DEFAULT_BATCH_SIZE);
        try (Connection con = jdbcTemplate.getDataSource().getConnection()) {
            con.setAutoCommit(false);
            int batchSize = batchArgs.size() / handleCount + 1;
            for (int i = 0; i < batchSize; i++) {
                int fromIndex = i * handleCount;
                int toIndex = Math.min(fromIndex + handleCount, batchArgs.size());
                List<Object[]> subList = batchArgs.subList(fromIndex, toIndex);
                jdbcTemplate.batchUpdate(updateOrInsertSQL, subList);
            }
            con.commit();
        } catch (Exception e) {
            logger.error("批量提交处理失败:{}", e.getMessage(), e);
            rollbackAndCloseConnection(con);
            throw new RuntimeException("批量提交处理失败", e);
        }
    }

    private static void rollbackAndCloseConnection(Connection con) {
        if (con != null) {
            try {
                con.rollback();
                con.setAutoCommit(true);
            } catch (SQLException e) {
                logger.error("无法回滚事务或重置自动提交状态", e);
            } finally {
                try {
                    con.close();
                } catch (SQLException e) {
                    logger.error("关闭数据库连接时发生错误", e);
                }
            }
        }
    }

    // 辅助类,用于传递实体和Mapper的组合
    private static class Pair<T, U> {
        private final T first;
        private final U second;

        public Pair(T first, U second) {
            this.first = first;
            this.second = second;
        }

        public T getFirst() {
            return first;
        }

        public U getSecond() {
            return second;
        }
    }
}

二、代码分析

这段代码是Java实现的一个批量处理工具类,主要包含了两个方法,分别用于MyBatis和JdbcTemplate的批量处理操作。这个类主要用于提高数据插入或更新的效率,通过批处理方式减少数据库交互次数,提高性能。
1. 类定义和成员变量
BATCH_SIZE: 定义了默认的批处理大小为1000。
logger: 使用SLF4J的日志记录器,用于输出日志信息。
sqlSessionFactory: 从Spring容器中获取的SqlSessionFactory,用于创建SqlSession实例。
2. 方法定义
2.1 batchUpdateOrInsert
这是一个泛型方法,接收一个数据列表、一个Mapper类以及一个BiFunction。BiFunction用于处理每个数据项,通常是调用Mapper的方法执行插入或更新操作。方法内部通过SqlSession的批处理功能执行操作,每次handleCount个数据项后提交一次事务。
2.2 jdbcTemplateBatchUpdateOrInsert
这个方法使用JdbcTemplate进行批量插入操作,接收一个JdbcTemplate实例、SQL语句和一个包含参数的列表。方法内部通过循环处理参数列表,每次处理handleCount个元素,然后调用batchUpdate方法执行批处理。在非事务环境中,手动提交事务;在事务环境中,提交操作无效。
3. 其他辅助方法
validateAndReturnBatchSize: 检查handleCount是否大于0,如果不是,则抛出异常,否则返回handleCount。此方法用于确保批处理大小的有效性。
rollbackAndCloseConnection: 用于回滚事务、恢复自动提交状态并关闭数据库连接,主要用于处理异常情况下的资源清理。
4. 总体评价
代码结构清晰,职责明确,分别实现了MyBatis和JdbcTemplate的批量处理功能。
使用了批处理和分批提交,提高了数据库操作的效率。
异常处理和资源管理比较完整,能够确保在异常情况下资源能够被正确释放。
通过BiFunction和Consumer提供了灵活的处理逻辑,可以适应不同的业务需求。
需要注意的是,这段代码假设了SpringUtils.getBean()方法可以从Spring上下文中获取SqlSessionFactory。如果实际项目中没有这样的工具类,需要使用Spring的依赖注入来获取SqlSessionFactory。

三、mybatis的foreach

实际开发中我们可能经常出现如下写法来完成数据的批量插入

<insert id="batchInsert" parameterType="java.util.List">  
    insert into USER (id, name) values  
    <foreach collection="list" item="model" index="index" separator=",">   
        (#{model.id}, #{model.name})  
    </foreach>  
</insert>

这个方法提升批量插入速度的原理是,将传统的:

INSERT INTO `user` (`name`, `age`)   
      VALUES ("zs", "10"),  
             ("ls", "12"),  
             ("ww", "14"),  
             ("hl", "11"),  
             ("sw", "54");

转化为:

INSERT INTO `user` (`name`, `age`)   
      VALUES ("zs", "10"),  
             ("ls", "12"),  
             ("ww", "14"),  
             ("hl", "11"),  
             ("sw", "54");

当插入数量很多时,不能一次性全放在一条语句里。因为默认执行器类型为Simple,会为每个语句创建一个新的预处理语句,也就是创建一个PreparedStatement对象。当我们不停地使用这个批量插入方法,而MyBatis对于含有的语句,无法采用缓存,那么在每次调用方法时,都会重新解析sql语句。

如果我们的foreach后有5000+个values,那么这个PreparedStatement特别长,他包含了很多占位符,对于占位符和参数的映射尤其耗时。查阅相关资料可知,values的增长与所需的解析时间,是呈指数型增长的。

mybatis的官方文档也给出了一种方式

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);  
try {  
    SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);  
    List<SimpleTableRecord> records = getRecordsToInsert(); // not shown  
   
    BatchInsert<SimpleTableRecord> batchInsert = insert(records)  
            .into(simpleTable)  
            .map(id).toProperty("id")  
            .map(firstName).toProperty("firstName")  
            .map(lastName).toProperty("lastName")  
            .map(birthDate).toProperty("birthDate")  
            .map(employed).toProperty("employed")  
            .map(occupation).toProperty("occupation")  
            .build()  
            .render(RenderingStrategy.MYBATIS3);  
   
    batchInsert.insertStatements().stream().forEach(mapper::insert);  
   
    session.commit();  
} finally {  
    session.close();  
}

基本思想是将 MyBatis session 的 executor type 设为 Batch ,然后多次执行插入语句。就类似于JDBC的下面语句一样。

Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=false&rewriteBatchedStatements=true","root","root");  
connection.setAutoCommit(false);  
PreparedStatement ps = connection.prepareStatement(  
        "insert into tb_user (name) values(?)");  
for (int i = 0; i < stuNum; i++) {  
    ps.setString(1,name);  
    ps.addBatch();  
}  
ps.executeBatch();  
connection.commit();  
connection.close();

所以推荐使用笔者给出的批处理方式,批量提交sql到数据库执行,显著提升性能。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是一个简单的MyBatis批量插入数据的工具类: ``` public class BatchInsertUtil { private static final int BATCH_SIZE = 1000; public static <T> void batchInsert(List<T> dataList, SqlSession sqlSession, String statement) { if (dataList == null || dataList.isEmpty()) { return; } int size = dataList.size(); int batchCount = size % BATCH_SIZE == 0 ? size / BATCH_SIZE : size / BATCH_SIZE + 1; int startIndex, endIndex; for (int i = 0; i < batchCount; i++) { startIndex = i * BATCH_SIZE; endIndex = Math.min((i + 1) * BATCH_SIZE, size); List<T> subList = dataList.subList(startIndex, endIndex); sqlSession.insert(statement, subList); } sqlSession.flushStatements(); } } ``` 使用方法如下: ``` SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); try { BatchInsertUtil.batchInsert(dataList, sqlSession, "com.example.mapper.insertBatch"); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); } finally { sqlSession.close(); } ``` 其中,`dataList`为待插入的数据列表,`sqlSession`为MyBatis的`SqlSession`对象,`statement`为对应的Mapper方法名称。要使用该工具类,需要在Mapper文件中定义对应的批量插入方法,例如: ``` <insert id="insertBatch" parameterType="java.util.List"> insert into my_table (column1, column2, column3) values <foreach collection="list" item="item" separator=","> (#{item.column1}, #{item.column2}, #{item.column3}) </foreach> </insert> ``` 该方法使用了MyBatis的`foreach`标签,将数据列表中的每一个元素都插入到数据库中。由于批量插入数据可能会造成内存溢出,因此在插入前需要将数据列表按照一定的大小分为多个批次插入,本例中批次大小为1000。在插入完成后需要调用`flushStatements()`方法来刷新缓存,确保所有的语句都已经发送到数据库。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值