在某个项目中,使用JPA的saveAll方法去批量写入数据时,通过打印sql,发现每次insert前都会先select一次,极大的浪费了写入性能。

分析一下代码,saveAll()

@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<S>(); for (S entity : entities) {
result.add(save(entity)); //在此处进行保存操作
} return result;
}

save()

@Transactional
public <S extends T> S save(S entity) { if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}

通过断点调试,可以发现是在判断isNew时候,进入了merge方法,总而造成先select,再写入,我个人理解其实是进行了update操作。

查看isNew方法的父类方法,在AbstractEntityInformation类中

public boolean isNew(T entity) {

    ID id = getId(entity);
Class<ID> idType = getIdType(); if (!idType.isPrimitive()) {
return id == null;
} if (id instanceof Number) {
return ((Number) id).longValue() == 0L;
} throw new IllegalArgumentException(String.format("Unsupported primitive id type %s!", idType));
}

可以看出,程序会判断实体的id是否为空,如果为空则是新数据,非空一般就是旧数据,进行update。

不过实际情况下,我的ID是UUID,并且也人为确认这个UUID在数据库中并不存在。那为何会出现这个问题呢?

看看我目前使用的实体类

package com.haramasu.simple_jpa.test.entity;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version; /**
* @author: Ding, Shuo
* @description:
* @create: 2019-03-14 11:04
**/
@Entity
public class School {
@Id
String id;
String name; public School() {
} public School(String id, String name) {
this.id = id;
this.name = name;
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

对于ID,使用的@Id注解,在构建实体对象的时候,ID需要我手动通过set方法或构造函数赋值。

尝试将ID的注解改为

@Id
@GenericGenerator(name = "id-generator", strategy = "uuid")
@GeneratedValue(generator = "id-generator")

再次尝试saveAll,断点处,isNew方法会判定id为null,本条数据是新数据,便不会再先进行select操作再insert。

猜想(暂不具体研究),是@GeneratedValue注解会在isNew方法执行后才对id进行赋值,而常规手动赋值ID,则会在isNew方法之前完成。

所以这就是第一种解决方法

但是有时候并没有合适的@GenericGenerator给我们使用,必须需要手动赋值ID,该如何实现?

通过StackOverflow搜索,发现第二种解决方法,可以通过在实体类中加入一个注解为@Version的属性,所以我的实体类现在长这样

@Id
String id;
String name;
@Version
private Long version;

使用版本号进行锁控制,此时判断isNew的时候就会比对version值,如果不为新,则不需要在select+insert了。

不过这种方法会使数据库表中多出一个字段,如果不希望出现多余字段的话。那么接下来就是第三种方法

参考方法一,进行自定义ID生成器

package com.haramasu.simple_jpa.test.generator;

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type; import java.io.Serializable;
import java.util.Properties;
import java.util.UUID; /**
* @author: Ding, Shuo
* @description:
* @create: 2019-03-14 13:34
**/
public class MyGenerator implements Configurable, IdentifierGenerator { @Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { } @Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
return UUID.randomUUID().toString();
}
}

修改实体类

package com.haramasu.simple_jpa.test.entity;

import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version; /**
* @author: Ding, Shuo
* @description:
* @create: 2019-03-14 11:04
**/
@Entity
public class School {
@Id
@GeneratedValue(generator = "id_generator")
@GenericGenerator(name = "id_generator",strategy = "com.haramasu.simple_jpa.test.generator.MyGenerator")
String id;
String name; public School(String name) {
this.name = name;
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

再次尝试,不会再出现select了。

如上就是三种解决新数据写入时候实际执行的时update操作的方法了。

JPA进行insert操作时会首先select吗的更多相关文章

  1. MyBatis魔法堂:Insert操作详解(返回主键、批量插入)

    一.前言    数据库操作怎能少了INSERT操作呢?下面记录MyBatis关于INSERT操作的笔记,以便日后查阅. 二. insert元素 属性详解   其属性如下: parameterType  ...

  2. 【转】Hive的insert操作

    insert 语法格式为: 1. 基本的插入语法: insert overwrite table tablename [partition(partcol1=val1,partclo2=val2)] ...

  3. IBatisNet:让insert操作返回新增记录的主键值

    项目引用ibatis包: IBatisNet.Common.dll --文件版本1.6.2.0 IBatisNet.DataAccess.dll IBatisNet.DataMapper.dll 项目 ...

  4. MyBatis魔法堂:Insert操作详解

    一.前言 数据库操作怎能少了INSERT操作呢?下面记录MyBatis关于INSERT操作的笔记,以便日后查阅. 二. insert元素 属性详解 其属性如下: parameterType:入参的全限 ...

  5. veridata实验例(3)验证veridata发现insert操作不会导致同步

    veridata实验例(3)验证veridata发现insert操作不会导致同步 续接:<veridata实验举例(2)验证表BONUS与表SALGRADE两节点同步情况>,地址:点击打开 ...

  6. 多表insert操作详解

    --1.无条件的多表insert all ; ; ; --没有条件,向多个目标表全量插入,必须有all insert all --不指定emp_1后面的列,也不指定values,那么emp_1中的所有 ...

  7. 使用sparkSQL的insert操作Kudu

    可以选择使用Spark SQL直接使用INSERT语句写入Kudu表:与'append'类似,INSERT语句实际上将默认使用UPSERT语义处理: import org.apache.kudu.sp ...

  8. 【spring data jpa】使用spring data jpa 的删除操作,需要加注解@Modifying @Transactional 否则报错如下: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call

    使用spring data jpa 的删除操作,需要加注解@Modifying     @Transactional 否则报错如下: No EntityManager with actual tran ...

  9. MyBatis学习 之 六、insert操作返回主键

       数据库操作怎能少了INSERT操作呢?下面记录MyBatis关于INSERT操作的笔记,以便日后查阅. 二. insert元素 属性详解   其属性如下: parameterType ,入参的全 ...

随机推荐

  1. python 运算符重复

  2. tar解压.tar.bz2文件失败:tar: Error is not recoverable: exiting now

    使用tar解压.tar.bz2文件: tar -jxvf xxxx.tar.bz2 报如下错误: 原因:未安装bzip yum -y install bzip2

  3. PAT天梯赛L1-002 打印漏斗

    题目链接:点击打开链接 本题要求你写个程序把给定的符号打印成沙漏的形状.例如给定17个"*",要求按下列格式打印 ***** *** * *** ***** 所谓"沙漏形 ...

  4. 【JZOJ4805】【NOIP2016提高A组模拟9.28】跟踪

    题目描述 输入 输出 样例输入 4 2 1 3 1 2 2 3 3 4 样例输出 2 数据范围 解法 预处理出两个陌生人走到各个点的距离. 从石神处开始dfs,判断走到每一个点是否会被抓: 如果会,则 ...

  5. fedora 安装ftp

    fedora默认不安装ftp服务(包括client程序/service程序),需要进行手动安装: yum install ftp(安装client) yum install vsftpd(安装serv ...

  6. jQuery动态加载动画spin.js

    在线演示 本地下载

  7. iOS 9 学习系列:Storyboard References

    http://www.cocoachina.com/ios/20150922/13474.html 如果你曾经使用 interface builder 创建过一个复杂.界面非常多的应用,你就会明白最后 ...

  8. 模拟登录新浪微博(Python) - 转

    Update: 如果只是写个小爬虫,访问需要登录的页面,采用填入cookie 的方法吧,简单粗暴有效,详细见:http://www.douban.com/note/264976536/模拟登陆有时需要 ...

  9. NLP+2vec︱认识多种多样的2vec向量化模型

    1.word2vec 耳熟能详的NLP向量化模型. Paper: https://papers.nips.cc/paper/5021-distributed-representations-of-wo ...

  10. 洛谷 P3768 简单的数学题 (莫比乌斯反演)

    题意:求$(\sum_{i=1}^{n}\sum_{j=1}^{n}ijgcd(i,j))mod p$(p为质数,n<=1e10) 很显然,推式子. $\sum_{i=1}^{n}\sum_{j ...