Spring JPA / Hibernate transaction force insert instead of update
编辑。在扩展基本存储库类并添加insert方法的同时,一个更好的解决方案似乎是在实体中实现Persistable。查看可能的解决方案2
我正在使用
遵循本教程的基础。
http://www.petrikainulainen.net/spring-data-jpa-tutorial/
我的实体存储库扩展了
我正在使用旧数据库,该数据库使用有意义的主键,而不是自动生成的ID
这种情况不应该真的发生,但是由于测试中的错误,我碰到了这种情况。订单表具有有意义的键OrderNumber(M000001等)。主键值在代码中生成,并在保存之前分配给对象。旧版数据库不使用自动生成的ID密钥。
我有一个正在创建新订单的交易。由于存在错误,我的代码生成了数据库中已存在的订单号(M000001)
执行repository.save会导致现有订单被更新。我想要的是强制执行插入操作,并由于重复的主键而使事务失败。
我可以在每个存储库中创建一个Insert方法,该方法在执行保存之前执行查找,如果该行存在则失败。一些实体具有带有OrderLinePK对象的复合主键,所以我不能使用基础spring FindOne(ID id)方法
在SpringJPA中,有没有一种干净的方法可以做到这一点?
我以前使用spring / Hibernate和我自己的基本存储库创建了一个不带jpa存储库的测试服务。我实现了一个Insert方法和一个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的建议
为实体实现
基础实体类,用于管理用于设置持久化标志的回调方法
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; } } |
然后,每个实体都必须实现
导入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。
我以前从未做过,但是稍加修改可能会完成任务。
实体有一个
那样,如果您实现
如我错了请纠正我。不幸的是,我现在无法在实时代码上对其进行测试。
关于
为什么不创建一个克隆对象,该对象将克隆除主键之外的所有内容,然后保存该克隆的对象。
由于PK将不存在,因此将发生插入而不是更新
这有帮助吗?
在PK列定义上设置
1 2 3 4 |
将您的ID设置为不可更新将阻止JPA对您的主键进行更新,因此就可以了。