Spring Data Jpa 实现批量插入

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")

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值