写完后才发现,日志里多条insert和批量插入,时间差不多是什么鬼?这个saveAll()有毒 🙃

问题描述

一组数据,批量插入,使用JPAsaveAll()方法,发现还是一条数据一条数据的插入

问题原因

@GeneratedValue

Spring Boot JPA Hibernate saveAll 速度慢原因调查与调优

搜了一下,大概原因和上面差不多,基本上在定义model时,都这样处理id

1
2
3
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@GeneratedValue(strategy = GenerationType.IDENTITY) 这个注解,会由hibernate主键策略生成器生成自增id,这样就不用自己控制id,所以存入数据时,数据一个一个获取id,导致数据一条一条插入,所以一个批量插入必要条件要自己设置数据id

缺少配置

配置spring.jpa.properties.hibernate.jdbc.batch_size=500
size根据自己需要设置

还建议开启一个配置
spring.jpa.properties.hibernate.generate_statistics=true
一个比较坑的点就是,就算你批量插入了数据,如果你开启spring.jpa.show-sql=true配置,你还是会看到无数条insert语句

通过generate_statistics配置你可以看到SQL总的执行情况

1
2
3
4
5
6
7
8
9
10
11
38864299 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
59624299 nanoseconds spent preparing 1 JDBC statements;
110488775 nanoseconds spent executing 1 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

batchs 那里就是批量插入情况

解决方案

  1. model里面ID不使用@GeneratedValue注解,在拼装数据时自己手动设置ID
  2. 开启spring.jpa.properties.hibernate.jdbc.batch_size配置

额外问题

实现了批量插入,但是可以看到日志里还是有无数条select语句,就想安安静静插个数据,咋就这么难呢?

搜了一下,发现了原因

1
2
3
4
5
6
7
8
9
10
11
12
13
@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
List<S> result = new ArrayList();
Iterator var3 = entities.iterator();

while(var3.hasNext()) {
S entity = var3.next();
result.add(this.save(entity));
}

return result;
}

saveAll()里调用了 save()方法

1
2
3
4
5
6
7
8
9
@Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}

根据网上的说法,isNew()为真的话,不会先select查询id是否存在,所以只要重写isNew()方法,就可以避免这种情况

1
2
3
4
5
6
7
8
9
10
@Entity
public class MyThing implements Persistable<T> {


...
@Override
public boolean isNew() {
return true;
}
}