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

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

问题描述

已编辑.虽然扩展基本存储库类并添加插入方法会起作用,但更优雅的解决方案似乎是在实体中实现 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 和 Hibernate 作为 ORM 使用 JpaTransactionManager 创建一个服务.

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

遵循此处的教程基础.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

在 Spring 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 的保存方法给出了我现在正在更新现有行的情况.

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

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

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.

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

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

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

}

自定义存储库工厂 bean

A custom repository factory 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)

<小时>

可能的解决方案 2

遵循 patrykos91

为实体实现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;
    }

}

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

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

导入 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() 实现时将用于评估"实体是否在数据库中是新的.基于该决定,EntityManager 应该决定在调用 .save().merge() 或 .persist()> 来自 Repository.

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天全站免登陆