1.问题原因
主要时间消耗在往MyBatis中批量插入数据。mapper configuration是用foreach循环做的,经过项目实践发现,当表的列数较多(20+),以及一次性插入的行数较多(5000+)时,整个插入的耗时十分漫长,达到了14分钟
2.问题代码
<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>
3.源码级别原因
mybatis默认执行器类型为Simple,会为每个语句创建一个新的预处理语句,也就是创建PreparedStatement对象。在我们的项目中,会不停地使用批量插入这个方法,而因为MyBatis对于含有<foreach>的语句,无法采用缓存,那么在每次调用方法时,都会重新解析sql语句,由于我foreach后有5000+个values,所以这个PreparedStatement特别长,包含了很多占位符,对于占位符和参数的映射尤其耗时。并且,查阅相关资料可知,values的增长与所需的解析时间,是呈指数型增长的
4.mybatis官方批量插入解决文档
https://mybatis.org/mybatis-dynamic-sql/docs/insert.html
5.解决问题中心思想
MyBatis session 的 executor type 设为 Batch ,然后多次执行插入语句。就类似于JDBC的下面语句一样。使用了 ExecutorType.BATCH 的插入方式,性能显著提升,不到 2s 便能全部插入完成
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();
6.结论
如果MyBatis需要进行批量插入,推荐使用 ExecutorType.BATCH 的插入方式,如果非要使用 <foreach>的插入的话,需要将每次插入的记录控制在 20~50 左右