Spring JPA/Hibernate事务强制插入而不是更新 [英] Spring JPA / Hibernate transaction force insert instead of update

查看:207
本文介绍了Spring JPA/Hibernate事务强制插入而不是更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

已编辑.在扩展基本存储库类并添加insert方法的同时,一个更优雅的解决方案似乎是在实体中实现Persistable.请参阅可能的解决方案2

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

我正在使用springframework.data.jpa创建一个服务,并使用JpaTransactionManager将Hibernate作为ORM.

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

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

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

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

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

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

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

这种情况本不应该真的发生,但是由于测试中的错误,我碰到了这种情况.订单表具有有意义的键OrderNumber(M000001等).主键值是用代码生成的,并在保存之前分配给对象.旧版数据库不使用自动生成的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.

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

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)

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

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.

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

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

在春季JPA中,有没有一种干净的方法?

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

我以前使用spring/Hibernate和我自己的基本存储库创建了一个不带jpa存储库的测试服务.我实现了一个Insert方法和一个Save方法,如下所示.

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.

这似乎工作正常. 使用getSession().saveOrUpdate的save方法提供了我现在正在更新现有行的体验.

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

使用getSession().save的插入方法失败,并重复了我想要的主键.

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

@Override
public Order save(Order bean) {

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

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


可能的解决方案1 ​​

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


Possible solution 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

可能不是最有效的,因为我们正在执行附加检索以在插入之前检查行是否存在,但这是主键.

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.

扩展存储库以添加除保存之外的insert方法.这是第一次切.

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

我必须将密钥以及实体传递给插入.我可以避免吗?

我实际上不希望返回数据.

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

}

自定义存储库工厂

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)


可能的解决方案2

遵循 patrykos91


Possible solution 2

Following the suggestion made by patrykos91

为实体实施Persistable接口,并覆盖isNew()

Implement the Persistable interface for the entities and override the 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;
    }

}

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

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

import 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;

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;

@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 + " ]";
    }



}

抽象出isNew()很好,但是我认为我做不到. getId不能作为实体具有不同的ID,如您所见,它具有复合PK.

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.

推荐答案

我以前从未做过,但是稍加修改,也许就可以完成这项工作.

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

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

There is a Persistable interface 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.

这样,如果实现isNew()始终返回true,则.persist()不应被称为什么,并且应在之后引发错误.

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.

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

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

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

这篇关于Spring JPA/Hibernate事务强制插入而不是更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆