Jpa提供的“批量插入”方法是 repository.saveAll(Iterable<S> iter)
repository.saveAll(Iterable<S> iter)
@Transactional
@Override
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null!");
List<S> result = new ArrayList<S>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
@Transactional
@Override
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) { // 根据id字段判断是插入还是更新
em.persist(entity);
return entity;
} else {
return em.merge(entity); // 如果id存在,还需要先去查询一下是否存在
}
}
从源码可以看出,虽然也是在一个事务内,但JPA的批量插入也是一条一条的插入,而且如果待插入对象中存在id则需要先查询一下判断是否真的存在,也就是说如果插入N条数据,则最多运行N条查询+N条插入,那么如何实现像jdbctemplate或者Mybatis那样,一条sql插入多条数据呢? 以减少多次传输来带的性能消耗
通用解决办法:
1.修改配置(yml自行修改格式)
spring.jpa.properties.hibernate.generate_statistics=true 这条可以输出执行情况
spring.jpa.properties.hibernate.jdbc.batch_size=100 这条是最重要的
spring.jpa.properties.hibernate.order_inserts=true
2.数据库连接配置添加
jdbc连接串最后加上 ?rewriteBatchedStatements=true
3.确认不使用自增主键,重点!一定不能使用GenerationType.IDENTITY!!
@Id
@GeneratedValue(strategy = GenerationType.AUTO/SEQUENCE) //不能使用GenerationType.IDENTITY
private Long id;
因为如果我们的实体使用GenerationType.IDENTITY生成器,Hibernate将默认禁用批量插入/更新。
多说几句,数据库的id策略如下:
数据库名称支持的id策略
mysql
GenerationType.TABLE
GenerationType.AUTO
GenerationType.IDENTITY
不支持GenerationType.SEQUENCE
oracle
strategy=GenerationType.AUTO
GenerationType.SEQUENCE
GenerationType.TABLE
不支持GenerationType.IDENTITY
postgreSQL
GenerationType.TABLE
GenerationType.AUTO
GenerationType.IDENTITY
GenerationType.SEQUENCE
都支持
因为MySQL不支持SEQUENCE,所以可以使用
@Id
@GeneratedValue(generator = "generator")
@GenericGenerator(name = "generator", strategy = "increment")
private Long id;
这种id生成策略是:在插入前先去获取Max(id),然后基于此分配id,但是一个hibernate实例就维护一个计数器,所以在多个实例运行的时候不能使用这个方法,文末的主键生成策略里有介绍各种方便选择(mysql的话为了实现batch insert可能只有uuid类的了,但是主键的存储消耗太大)
登录数据库观察日志
SET global general_log = 1;
SET global log_output = 'table';
select * from mysql.general_log;
SET global general_log = 0;(观察结束后记得关闭)
修改前调用saveAll:
| 2020-05-12 06:30:37.942243 | oagsm[oagsm] @ [172.17.0.1] | 216 | 1 | Query | SELECT @@session.transaction_read_only
| 2020-05-12 06:30:42.304615 | oagsm[oagsm] @ [172.17.0.1] | 207 | 1 | Query | insert into store (created_by, created_date, updated_by, updated_date, address, business_hours, business_scope, commissioner_name, dept_id, dept_name, latitude, logo, longitude, mobile, qr_code, remark, state, store_name, type) values (null, '2020-05-12 06:30:42.251', null, '2020-05-12 06:30:42.251', null, null, null, 'test', null, null, null, null, null, null, null, null, 2, 'test0', 1)
| 2020-05-12 06:30:42.313706 | oagsm[oagsm] @ [172.17.0.1] | 207 | 1 | Query | insert into store (created_by, created_date, updated_by, updated_date, address, business_hours, business_scope, commissioner_name, dept_id, dept_name, latitude, logo, longitude, mobile, qr_code, remark, state, store_name, type) values (null, '2020-05-12 06:30:42.282', null, '2020-05-12 06:30:42.282', null, null, null, 'test', null, null, null, null, null, null, null, null, 1, 'test1', 1)
| 2020-05-12 06:30:42.319299 | oagsm[oagsm] @ [172.17.0.1] | 207 | 1 | Query | insert into store (created_by, created_date, updated_by, updated_date, address, business_hours, business_scope, commissioner_name, dept_id, dept_name, latitude, logo, longitude, mobile, qr_code, remark, state, store_name, type) values (null, '2020-05-12 06:30:42.287', null, '2020-05-12 06:30:42.287', null, null, null, 'test', null, null, null, null, null, null, null, null, 3, 'test2', 1)
.....省略n条.....
| 2020-05-12 06:30:42.875216 | oagsm[oagsm] @ [172.17.0.1] | 207 | 1 | Query | commit
修改后调用saveAll:
| 2020-05-12 06:32:12.399584 | oagsm[oagsm] @ [172.17.0.1] | 217 | 1 | Query | SELECT @@session.transaction_read_only
| 2020-05-12 06:32:12.406255 | oagsm[oagsm] @ [172.17.0.1] | 217 | 1 | Query | insert into store (address, business_hours, business_scope, commissioner_name, created_by, created_date, dept_id, dept_name, latitude, logo, longitude, mobile, qr_code, remark, state, store_name, type, updated_by, updated_date, id) values (null, null, null, 'test', null, '2020-05-12 06:32:12.239', null, null, null, null, null, null, null, null, 2, 'test0', 1, null, '2020-05-12 06:32:12.239', 6),(null, null, null, 'test', null, '2020-05-12 06:32:12.264', null, null, null, null, null, null, null, null, 1, 'test1', 1, null, '2020-05-12 06:32:12.264', 7)....省略n条....
| 2020-05-12 06:32:12.411352 | oagsm[oagsm] @ [172.17.0.1] | 217 | 1 | Query | commit
性能比较:测试批量插入100条数据,前者平均耗时600ms,后者150ms
另外解释一下主键策略生成器:
@GenericGenerator hibernate主键策略生成器
@GenericGenerator注解配合@GeneratedValue一起使用,@GeneratedValue注解中的"generator"属性要与@GenericGenerator注解中name属性一致,strategy属性表示hibernate的主键生成策略
native: 对于 oracle 采用 Sequence 方式,对于MySQL 和 SQL Server 采用identity(自增主键生成机制),native就是将主键的生成工作交由数据库完成,hibernate不管(很常用)。 -> sequence+identity
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "native")
uuid: 采用128位的uuid算法生成主键,uuid被编码为一个32位16进制数字的字符串。占用空间大(字符串类型)。
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "uuid")
hilo: 使用hilo生成策略,要在数据库中建立一张额外的表,默认表名为hibernate_unique_key,默认字段为integer类型,名称是next_hi(比较少用)。
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "hilo")
assigned: 在插入数据的时候主键由程序处理(很常用),这是 <generator>元素没有指定时的默认生成策略。等同于JPA中的AUTO。
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "assigned ")
identity: 使用SQL Server 和 MySQL 的自增字段,这个方法不能放到 Oracle 中,Oracle 不支持自增字段,要设定sequence(MySQL 和 SQL Server 中很常用); 等同于JPA中的INDENTITY。
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "identity ")
sequence: 调用底层数据库的序列来生成主键,要设定序列名,不然hibernate无法找到。 oracle/postgresql
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "sequence", parameters = { @Parameter(name = "sequence", value = "底层数据库sequenceName") }
increment: 插入数据的时候hibernate会给主键添加一个自增的主键,但是一个hibernate实例就维护一个计数器,所以在多个实例运行的时候不能使用这个方法。 mysql/postgresql
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "increment")
guid: 采用数据库底层的guid算法机制,对应MYSQL的uuid()函数,SQL Server的newid()函数,ORACLE的rawtohex(sys_guid())函数等。
@GeneratedValue(generator = "xxx") @GenericGenerator(name = "xxx", strategy = "guid")