问题:
我们使用JPA的saveAll的时候,会发现这个方法特别慢,我在本地做的测试是插入10万条数据的时候,耗时51503ms,和JDBC方式的批量处理耗时对比后就会发现,性能差了百倍,所以我们要优化下我们的代码。
一:修改setId的策略
我们观察就会发现,在saveAll传入的对象里面,设置了id的情况下,JPA会根据id去执行一条select语句,数据量越多,和数据库交互次数越多,这个操作会极大的拖慢性能,那我们首要解决的,就是不让JPA去执行select。解决办法有两个,麻烦一点的是这样的,实现Persistable接口,重写isNew方法,返回true时,就不会去执行select。如下代码所示,我们在给对象setId之前,调用下preInsert方法,这样就将isNew设置为true了。
@Entity
@Table(name = "user")
@Data
public class User implements Persistable {
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "phone_number")
private String phoneNumber;
@Transient
private boolean isNewFlag;
public void preInsert(){
this.isNewFlag = true;
}
@Override
public boolean isNew() {
return isNewFlag;
}
}
还有一种实现方式是我们将手动setId改到在实体里通过注解来设置,如下所示:
@Id
@GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
@Column(name = "id")
private String id;
这样优化后,时间缩短到了24260ms。性能还是很差,这是因为JPA的配置里没有开启批量操作。
二:开启JPA批量操作
mysql的话,开启批量操作需要在jdbc的url后面加上参数rewriteBatchedStatements=true,oracle无需此操作。开启JPA的批量操作需要在yml里加上如下配置
jpa:
properties:
hibernate:
jdbc:
batch_size: 500
order_inserts: true
order_updates: true
tips:batch_size不宜配置的太大,并不是越大,性能越好。
这样设置后,时间缩短到了2915ms,性能提升了大约20倍。但这样和JDBC方式还是有很大的差距,如果对性能还不满意的,可以改用JDBC的批处理方式。
三:终极优化JdbcTemplate
在Spring里,我们可以使用JdbcTemplate来简化JDBC的操作,然后通过执行jdbcTemplate里的batchUpdate方法,我们只需要传入sql语句,然后再设置下参数,就可以轻松实现批量操作。
@Autowired
private UserRepository userRepository;
public void batchInsert(List list){
String sql = "insert into user(id, name , phone_number) values(?, ?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
preparedStatement.setString(1, list.get(i).getId());
preparedStatement.setString(2, list.get(i).getName());
preparedStatement.setString(3, list.get(i).getPhoneNumber());
}
@Override
public int getBatchSize() {
return list.size();
}
});
}
这种方式改造后,批量插入的耗时是185ms,性能提升200倍。。