Spring JPA/Hibernate 事务强制插入而不是更新
Spring JPA / Hibernate transaction force insert instead of update
已编辑。虽然扩展基础存储库 class 并添加插入方法会起作用,但更优雅的解决方案似乎是在实体中实现 Persistable。请参见可能的解决方案 2
我正在使用 springframework.data.jpa
创建一个服务,使用 JpaTransactionManager
将 Hibernate 作为 ORM。
按照这里教程的基础。
http://www.petrikainulainen.net/spring-data-jpa-tutorial/
我的实体存储库扩展 org.springframework.data.repository.CrudRepository
我正在使用一个遗留数据库,它使用有意义的主键而不是自动生成的 ID
这种情况应该不会真的发生,但是我在测试中遇到了一个错误。 Order table 有一个有意义的 OrderNumber 关键字(M000001 等)。主键值在代码中生成并在保存之前分配给对象。遗留数据库不使用自动生成的 ID 密钥。
我有一个正在创建新订单的交易。由于错误,我的代码生成了一个数据库中已经存在的订单号 (M000001)
执行 repository.save 导致更新现有订单。我想要的是强制插入并由于重复的主键而使事务失败。
我可以在每个存储库中创建一个 Insert 方法,该方法在执行保存之前执行查找,如果该行存在则失败。某些实体具有带 OrderLinePK 对象的复合主键,因此我无法使用基础 spring FindOne(ID id) 方法
在 spring JPA 中是否有一种简洁的方法可以做到这一点?
我之前使用 spring/Hibernate 和我自己的基础存储库创建了一个没有 jpa 存储库的测试服务。我实现了一个 Insert 方法和一个 Save 方法,如下所示。
这似乎工作正常。
使用 getSession().saveOrUpdate
的保存方法给出了我现在正在更新现有行的情况。
使用 getSession().save
的插入方法因我想要的重复主键而失败。
@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
可能不是最有效的,因为我们正在执行额外的检索以在插入之前检查行是否存在,但它是主键。
扩展存储库以在保存之外添加插入方法。这是第一次剪辑。
我必须将密钥传递到插入和实体中。我可以避免这种情况吗?
我其实并不想要返回数据。 entitymanager 没有 exists 方法(是否存在只是做一个计数(*)来检查行的存在?)
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);
}
实施:自定义存储库基础 class。
注意:如果我走这条路,将创建一个自定义异常类型..
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
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;
}
}
}
最后在配置
中连接自定义存储库基础class
// 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()
一个基础实体 class 用于管理回调方法以设置持久化标志
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;
@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。
为什么不创建一个克隆对象来克隆除主键之外的所有内容,然后保存该克隆对象。
由于主键不存在,因此发生插入而不是更新
我以前从来没有这样做过,但稍微 hack 一下,也许就能完成这项工作。
实体有一个 Persistable
接口。它有一个方法boolean isNew()
,如果实体在数据库中是新的还是不存在,那么在实现时将用于"assess"。基于该决定,EntityManager 应该决定在您从 Repository
调用 .save()
之后对该实体调用 .merge()
或 .persist()
。
那样的话,如果您将 isNew()
实现为始终 return 为真,那么无论如何都应该调用 .persist()
,之后应该抛出错误。
如果我错了请纠正我。不幸的是,我现在无法在实时代码上对其进行测试。
关于 Persistable
的文档:http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html
这有帮助吗?
在您的 PK 列定义上设置 updatable = false
。示例:
@Id
@GeneratedValue
@Column(name = “id”, updatable = false, nullable = false)
private Long id;
将您的 ID 设置为不可更新将阻止 JPA 对您的主键进行更新,所以就这样吧。
已编辑。虽然扩展基础存储库 class 并添加插入方法会起作用,但更优雅的解决方案似乎是在实体中实现 Persistable。请参见可能的解决方案 2
我正在使用 springframework.data.jpa
创建一个服务,使用 JpaTransactionManager
将 Hibernate 作为 ORM。
按照这里教程的基础。 http://www.petrikainulainen.net/spring-data-jpa-tutorial/
我的实体存储库扩展 org.springframework.data.repository.CrudRepository
我正在使用一个遗留数据库,它使用有意义的主键而不是自动生成的 ID
这种情况应该不会真的发生,但是我在测试中遇到了一个错误。 Order table 有一个有意义的 OrderNumber 关键字(M000001 等)。主键值在代码中生成并在保存之前分配给对象。遗留数据库不使用自动生成的 ID 密钥。
我有一个正在创建新订单的交易。由于错误,我的代码生成了一个数据库中已经存在的订单号 (M000001)
执行 repository.save 导致更新现有订单。我想要的是强制插入并由于重复的主键而使事务失败。
我可以在每个存储库中创建一个 Insert 方法,该方法在执行保存之前执行查找,如果该行存在则失败。某些实体具有带 OrderLinePK 对象的复合主键,因此我无法使用基础 spring FindOne(ID id) 方法
在 spring JPA 中是否有一种简洁的方法可以做到这一点?
我之前使用 spring/Hibernate 和我自己的基础存储库创建了一个没有 jpa 存储库的测试服务。我实现了一个 Insert 方法和一个 Save 方法,如下所示。
这似乎工作正常。
使用 getSession().saveOrUpdate
的保存方法给出了我现在正在更新现有行的情况。
使用 getSession().save
的插入方法因我想要的重复主键而失败。
@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
可能不是最有效的,因为我们正在执行额外的检索以在插入之前检查行是否存在,但它是主键。
扩展存储库以在保存之外添加插入方法。这是第一次剪辑。
我必须将密钥传递到插入和实体中。我可以避免这种情况吗?
我其实并不想要返回数据。 entitymanager 没有 exists 方法(是否存在只是做一个计数(*)来检查行的存在?)
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);
}
实施:自定义存储库基础 class。 注意:如果我走这条路,将创建一个自定义异常类型..
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
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;
}
}
}
最后在配置
中连接自定义存储库基础class// 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()
一个基础实体 class 用于管理回调方法以设置持久化标志
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;
@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。
为什么不创建一个克隆对象来克隆除主键之外的所有内容,然后保存该克隆对象。
由于主键不存在,因此发生插入而不是更新
我以前从来没有这样做过,但稍微 hack 一下,也许就能完成这项工作。
实体有一个 Persistable
接口。它有一个方法boolean isNew()
,如果实体在数据库中是新的还是不存在,那么在实现时将用于"assess"。基于该决定,EntityManager 应该决定在您从 Repository
调用 .save()
之后对该实体调用 .merge()
或 .persist()
。
那样的话,如果您将 isNew()
实现为始终 return 为真,那么无论如何都应该调用 .persist()
,之后应该抛出错误。
如果我错了请纠正我。不幸的是,我现在无法在实时代码上对其进行测试。
关于 Persistable
的文档:http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html
这有帮助吗?
在您的 PK 列定义上设置 updatable = false
。示例:
@Id
@GeneratedValue
@Column(name = “id”, updatable = false, nullable = false)
private Long id;
将您的 ID 设置为不可更新将阻止 JPA 对您的主键进行更新,所以就这样吧。