1. 需求描述
有一个耗时较长的Job存在CPU占用过高的问题,经排查发现,主要时间消耗向MyBatis中批量插入数据。mapper configuration是用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>
2. 原理分析
Mybatis底层基于JDBC实现,在执行insert操作的时候,默认执行类型为Simple,会为每个语句创建一个新的预处理语句,也就是创建一个PrepareStatement对象。而因为Mybatis对于含有<foreach>
的语句,无法采用缓存,那么在每次循环时,都会重新解析sql语句。拼装PrepareStatement对象十分耗时,因为拼装过程中对于占位符和参数的映射非常耗时间。
3. 优化代码
try(SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
// 表数据对应的mapper
SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);
// 将你想插入的大批量数据封装到这个records 集合中,SimpleTableRecord为数据的类型
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(RenderingStrategies.MYBATIS3);
batchInsert.insertStatements().forEach(mapper::insert);
session.commit();
}finally{
session.close();
}
- 优化方式:通过Batch Insert Support(Mybatis推荐),参考文档https://mybatis.org/mybatis-dynamic-sql/docs/insert.html
- 和foreach的区别:Batch Insert Support的基本思想是将 MyBatis session 的 executor type 设为 Batch ,然后多次执行插入语句。效率更高。
- 在实际工作中一次插入20-50条,用
<foreach>
,插入数据量较多时更推荐用Batch Insert Support形式。