zoukankan      html  css  js  c++  java
  • JPA进行insert操作时会首先select吗

    在某个项目中,使用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操作的方法了。

  • 相关阅读:
    UCML破解
    UCML快速开发平台学习1-UCML环境安装
    window系统安装jdk,jre
    解决Android报错No resource found that matches the given name (at 'text' with value '@string/hello').
    es6小技巧整理
    如何将项目推到github上面
    如何将时间格式化
    uni-app 页面跳转的两种方法
    layui表单校验及监听复选框选中状态的坑
    使用nvm管理多个不同版本的nodeJS之安装成功nodeJs之后使用npm报错的问题
  • 原文地址:https://www.cnblogs.com/tilv37/p/10529853.html
Copyright © 2011-2022 走看看