前言
JPA作为主流的持久层框架,被广大程序员所使用,基本的使用方法请参考JPA的常见用法。虽然JPA在编码上提供了很多的便利,但是其接口提供的插入数据的方法效率非常低,下面就简单介绍下效率低的原因和解决办法。
save和saveAll方法
单从方法签名来看,save
方法用来插入单个数据,而saveAll
方法插入多个数据。但是仔细看saveAll
的方法实现,会发现saveAll
里面是循环调用save
方法来插入数据,所以很自然地以为saveAll
和save
方法插入数据的时间差不多。但是实际测试的结果却表明两者的差距在一到两倍左右,以下是同一机器上的测试数据(1k),单位毫秒:
save | saveAll |
---|---|
182162 | 73579 |
那么,既是循环调用,两者应该差距不大才对,怎么会差距这么大呢?原因是事务。仔细看源码会发现,save
方法上有@Transactional
注解,而saveAll
上也有@Transactional
注解。对于嵌套事务,要根据事务的传播特性(spring默认是REQUIRED
,即支持当前事务,如果不存在,则创建一个新事务。)决定事务的行为。这也是为啥saveAll
会比save
快很多的原因:循环里的save
方法复用外层saveAll
方法的事务,节省了很多事务方面的开销。
persist和merge
尽管saveAll
方法在插入性能上提升了很多,但是和直接用jdbc
的批量插入相比还有差距,那么原因是什么呢?仔细观察程序运行过程中控制台输出的sql语句,会发现save
方法调用时,会先查询后插入。对于确定是新数据的对象,也会查询,这也正是save
方法的实现。但是插入之前的查询确实影响了很多的性能,如果可以,对于确定是新的记录,能不能不要查询步骤呢?答案是可以的,那就是直接使用EntityManager
的persist
方法。对于确定是已经存在于系统的记录,直接使用merge
。这样少了一个查询操作,就可以显著提升插入的效率。
for (int i = 0; i < poList.size(); i++) {
PO po = poList.get(i);
entityManager.persist(po);
if ((i+1) % 1024 == 0 || i == poList.size()) {
//及时保存到数据
entityManager.flush();
//及时清理缓存,避免堆溢出
entityManager.clear();
}
}
JDBC
追求极致速度时,可以使用设置连接的自动提交为false
,即setAutoCommit(false)
,手动管理事务。批量赋值之后,再统一提交或者回滚。
try{
connection.setAutoCommit(false);
//以下是批量赋值
...
connection.commit();
}catch(Exception e){
connection.rollback();
}finally{
connection.close();
}
参考JDBC工具组件DBUtils项目。
JdbcTemplate
连接url配置rewriteBatchedStatements=true
参数。
结论
增删改尽量用JdbcTemplate
或JDBC
,查询才使用JPA
。
批量增删改效果不佳时,检查链接url是否添加了rewriteBatchedStatements=true参数。