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
Spring JPA / Hibernate transaction force insert instead of update
提问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.jpa
with Hibernate as the ORM using JpaTransactionManager
.
我正在创建一个使用springframework.data.jpa
Hibernate 作为 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().saveOrUpdate
gave what I'm experiencing now with an existing row being updated.
这似乎工作正常。使用的保存方法getSession().saveOrUpdate
给出了我现在正在更新的现有行的体验。
The insert method using getSession().save
failed 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 Persistable
interface 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 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
.
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
关于文档Persistable
:http: //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 = false
on 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 对您的主键进行更新,所以就这样吧。