java Spring JPA/Hibernate 事务强制插入而不是更新

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37253175/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 02:21:03  来源:igfitidea点击:

Spring JPA / Hibernate transaction force insert instead of update

javaspringhibernatespring-dataspring-data-jpa

提问by MartinS

Edited. Whilst extending the base repository class and adding an insert method would work an more elegant solution appears to be implementing Persistable in the entities. See Possible Solution 2

已编辑。虽然扩展基本存储库类并添加插入方法会起作用,但更优雅的解决方案似乎是在实体中实现 Persistable。请参阅可能的解决方案 2



I'm creating a service using springframework.data.jpawith Hibernate as the ORM using JpaTransactionManager.

我正在创建一个使用springframework.data.jpaHibernate 作为 ORM的服务JpaTransactionManager

following the basis of the tutorial here. http://www.petrikainulainen.net/spring-data-jpa-tutorial/

遵循本教程的基础here。 http://www.petrikainulainen.net/spring-data-jpa-tutorial/

My entity repositories extend org.springframework.data.repository.CrudRepository

我的实体存储库扩展 org.springframework.data.repository.CrudRepository

I'm working with a legacy database which uses meaningful primary keys rather then auto generated id's

我正在使用一个旧数据库,它使用有意义的主键而不是自动生成的 id

This situation shouldn't really occur, but I came across it due to a bug in testing. Order table has a meaningful key of OrderNumber (M000001 etc). The primary key value is generated in code and assigned to the object prior to save. The legacy database does not use auto-generated ID keys.

这种情况不应该真正发生,但由于测试中的错误,我遇到了它。订单表有一个有意义的 OrderNumber 键(M000001 等)。主键值在代码中生成并在保存之前分配给对象。旧数据库不使用自动生成的 ID 密钥。

I have a transaction which is creating a new order. Due to a bug, my code generated an order number which already existed in the database (M000001)

我有一个正在创建新订单的交易。由于错误,我的代码生成了一个已存在于数据库中的订单号 (M000001)

Performing a repository.save caused the existing order to be updated. What I want is to force an Insert and to fail the transaction due to duplicate primary key.

执行repository.save 会导致更新现有订单。我想要的是强制插入并由于主键重复而使事务失败。

I could create an Insert method in every repository which performs a find prior to performing a save and failing if the row exists. Some entities have composite primary keys with a OrderLinePK object so I can't use the base spring FindOne(ID id) method

我可以在每个存储库中创建一个 Insert 方法,该方法在执行保存之前执行查找,如果该行存在则失败。某些实体具有带有 OrderLinePK 对象的复合主键,因此我无法使用基本 spring FindOne(ID id) 方法

Is there a clean way of doing this in spring JPA?

在 Spring JPA 中是否有一种干净的方法来做到这一点?

I previously created a test service without jpa repository using spring/Hibernate and my own base repository. I implemented an Insert method and a Save method as follows.

我之前使用 spring/Hibernate 和我自己的基础存储库创建了一个没有 jpa 存储库的测试服务。我实现了一个 Insert 方法和一个 Save 方法,如下所示。

This seemed to work OK. The save method using getSession().saveOrUpdategave what I'm experiencing now with an existing row being updated.

这似乎工作正常。使用的保存方法getSession().saveOrUpdate给出了我现在正在更新的现有行的体验。

The insert method using getSession().savefailed with duplicate primary key as I want.

getSession().save如我所愿,使用重复主键的插入方法失败。

@Override
public Order save(Order bean) {

    getSession().saveOrUpdate(bean);
    return bean;
}

@Override
public Order insert(Order bean) {
    getSession().save(bean);
    return bean;
}


Possible solution 1

可能的解决方案 1

Based on chapter 1.3.2 of the spring docs here http://docs.spring.io/spring-data/jpa/docs/1.4.1.RELEASE/reference/html/repositories.html

基于此处的 spring 文档的第 1.3.2 章 http://docs.spring.io/spring-data/jpa/docs/1.4.1.RELEASE/reference/html/repositories.html

Probably not the most efficient as we're doing an additional retrieval to check the existence of the row prior to insert, but it's primary key.

可能不是最有效的,因为我们正在执行额外的检索以在插入之前检查行的存在,但它是主键。

Extend the repository to add an insert method in addition to save. This is the first cut.

除了保存之外,扩展存储库以添加插入方法。这是第一次裁员。

I'm having to pass the key into the insert as well as the entity. Can I avoid this ?

我必须将密钥传递到插入以及实体中。我可以避免这种情况吗?

I don't actually want the data returned. the entitymanager doesn't have an exists method (does exists just do a count(*) to check existence of a row?)

我实际上不希望返回数据。entitymanager 没有存在方法(是否存在只是执行计数(*)来检查行是否存在?)

import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

/**
 *
 * @author Martins
 */
@NoRepositoryBean
public interface IBaseRepository <T, ID extends Serializable> extends JpaRepository<T, ID> {

    void insert(T entity, ID id);    

}

Implementation : Custom repository base class. Note : A custom exception type will be created if I go down this route..

实现:自定义存储库基类。注意:如果我沿着这条路线走,将创建一个自定义异常类型..

import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;


public class BaseRepositoryImpl<T, ID extends Serializable> 
        extends SimpleJpaRepository<T, ID> implements IBaseRepository<T, ID> {

    private final EntityManager entityManager;

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.entityManager = em;
    }


    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super (entityInformation, entityManager);
        this.entityManager = entityManager;

    }

    @Transactional
    public void insert(T entity, ID id) {

        T exists = entityManager.find(this.getDomainClass(),id);

        if (exists == null) {
          entityManager.persist(entity);
        }
        else 
          throw(new IllegalStateException("duplicate"));
    }    

}

A custom repository factory bean

自定义存储库工厂 bean

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

/**
 * This factory bean replaces the default implementation of the repository interface 
 */
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
  extends JpaRepositoryFactoryBean<R, T, I> {

  protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {

    return new BaseRepositoryFactory(entityManager);
  }

  private static class BaseRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {

    private EntityManager entityManager;

    public BaseRepositoryFactory(EntityManager entityManager) {
      super(entityManager);

      this.entityManager = entityManager;
    }

    protected Object getTargetRepository(RepositoryMetadata metadata) {

      return new BaseRepositoryImpl<T, I>((Class<T>) metadata.getDomainType(), entityManager);
    }

    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {

      // The RepositoryMetadata can be safely ignored, it is used by the JpaRepositoryFactory
      //to check for QueryDslJpaRepository's which is out of scope.
      return IBaseRepository.class;
    }
  }
}

Finally wire up the custom repository base class in the configuration

最后在配置中连接自定义存储库基类

// Define this class as a Spring configuration class
@Configuration

// Enable Spring/jpa transaction management.
@EnableTransactionManagement

@EnableJpaRepositories(basePackages = {"com.savant.test.spring.donorservicejpa.dao.repository"}, 
        repositoryBaseClass = com.savant.test.spring.donorservicejpa.dao.repository.BaseRepositoryImpl.class)


Possible solution 2

可能的解决方案 2

Following the suggestion made by patrykos91

遵循patrykos91提出的建议

Implement the Persistableinterface for the entities and override the isNew()

实现Persistable实体的接口并覆盖isNew()

A base entity class to manage the callback methods to set the persisted flag

用于管理回调方法以设置持久标志的基本实体类

import java.io.Serializable;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;


@MappedSuperclass
public abstract class BaseEntity implements Serializable{

    protected transient boolean persisted;


    @PostLoad
    public void postLoad() {
        this.persisted = true;
    }

    @PostUpdate
    public void postUpdate() {
        this.persisted = true;
    }

    @PostPersist
    public void postPersist() {
        this.persisted = true;
    }

}

Then each entity must then implement the isNew()and getID()

然后每个实体都必须实施isNew()getID()

import java.io.Serializable; import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Table; import javax.xml.bind.annotation.XmlRootElement; import org.springframework.data.domain.Persistable;

导入 java.io.Serializable; 导入 javax.persistence.Column; 导入 javax.persistence.EmbeddedId; 导入 javax.persistence.Entity; 导入 javax.persistence.Table; 导入 javax.xml.bind.annotation.XmlRootElement; 导入 org.springframework.data.domain.Persistable;

@Entity
@Table(name = "MTHSEQ")
@XmlRootElement

public class Sequence extends BaseEntity implements Serializable, Persistable<SequencePK> {

    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected SequencePK sequencePK;
    @Column(name = "NEXTSEQ")
    private Integer nextseq;

    public Sequence() {
    }


    @Override
    public boolean isNew() {
        return !persisted;
    }

    @Override
    public SequencePK getId() {
        return this.sequencePK;
    }



    public Sequence(SequencePK sequencePK) {
        this.sequencePK = sequencePK;
    }

    public Sequence(String mthkey, Character centre) {
        this.sequencePK = new SequencePK(mthkey, centre);
    }

    public SequencePK getSequencePK() {
        return sequencePK;
    }

    public void setSequencePK(SequencePK sequencePK) {
        this.sequencePK = sequencePK;
    }

    public Integer getNextseq() {
        return nextseq;
    }

    public void setNextseq(Integer nextseq) {
        this.nextseq = nextseq;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (sequencePK != null ? sequencePK.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Sequence)) {
            return false;
        }
        Sequence other = (Sequence) object;
        if ((this.sequencePK == null && other.sequencePK != null) || (this.sequencePK != null && !this.sequencePK.equals(other.sequencePK))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.savant.test.spring.donorservice.core.entity.Sequence[ sequencePK=" + sequencePK + " ]";
    }



}

It would be nice to abstract out the isNew() but I don't think I can. The getId can't as entities have different Id's, as you can see this one has composite PK.

抽象出 isNew() 会很好,但我认为我不能。getId 不能,因为实体具有不同的 Id,如您所见,该实体具有复合 PK。

采纳答案by patrykos91

I never did that before, but a little hack, would maybe do the job.

我以前从来没有这样做过,但是一点点黑客,也许可以完成这项工作。

There is a Persistableinterface for the entities. It has a method boolean isNew()that when implemented will be used to "assess" if the Entity is new or not in the database. Base on that decision, EntityManager should decide to call .merge()or .persist()on that entity, after You call .save()from Repository.

Persistable实体有一个接口。它有一个方法boolean isNew(),在实现时将用于“评估”实体是否在数据库中是新的。根据该决定,EntityManager 应决定在您从调用之后调用.merge().persist()对该实体。.save()Repository

Going that way, if You implement isNew()to always return true, the .persist()should be called no mater what, and error should be thrown after.

按照这种方式,如果您实现isNew()始终返回 true,则.persist()无论如何都应该调用 ,并且应该在之后抛出错误。

Correct me If I'm wrong. Unfortunately I can't test it on a live code right now.

如我错了请纠正我。不幸的是,我现在无法在实时代码上对其进行测试。

Documentation about Persistable: http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html

关于文档Persistablehttp: //docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html

回答by madhusudan rao Y

Why not create a clone object which clones everything except your primary keys and then save this cloned object.

为什么不创建一个克隆对象来克隆除主键之外的所有内容,然后保存这个克隆对象。

Since the PK will not be present, an insert happens, instead of an update

由于 PK 将不存在,因此会发生插入,而不是更新

回答by Daniel C.

Does this help?

这有帮助吗?

Set updatable = falseon your PK column definition. Example:

updatable = false在您的 PK 列定义上设置。例子:

@Id
@GeneratedValue
@Column(name = “id”, updatable = false, nullable = false)
private Long id;

Setting your id non updatable will stop JPA from doing updates on your primary key, so thing about it.

设置您的 id 不可更新将阻止 JPA 对您的主键进行更新,所以就这样吧。