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

Spring JPA / Hibernate transaction force insert instead of update

编辑。在扩展基本存储库类并添加insert方法的同时,一个更好的解决方案似乎是在实体中实现Persistable。查看可能的解决方案2

我正在使用springframework.data.jpa和Hibernate作为JpaTransactionManager的ORM创建服务。

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

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

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

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

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

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

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

在SpringJPA中,有没有一种干净的方法可以做到这一点?

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

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

使用getSession().save的插入方法由于需要重复主键失败。

1
2
3
4
5
6
7
8
9
10
11
12
@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

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

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

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

我实际上不希望返回数据。这个entitymanager没有一个exist方法(是否存在,只是做一个count(*)来检查一行是否存在?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);    

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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;
    }
  }
}

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

1
2
3
4
5
6
7
8
// 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()

基础实体类,用于管理用于设置持久化标志的回调方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()

导入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;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@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。


我以前从未做过,但是稍加修改可能会完成任务。

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

那样,如果您实现isNew()始终返回true,则.persist()不应被称为任何东西,并且应该引发错误。

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

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


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

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


这有帮助吗?

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

1
2
3
4
@Id
@GeneratedValue
@Column(name ="id", updatable = false, nullable = false)
private Long id;

将您的ID设置为不可更新将阻止JPA对您的主键进行更新,因此就可以了。