英文:
hibernate guarantee failure on concurrent insertion of existing record instead of update
问题
我有这种情景。
我有一个Web/REST服务,它接收一个带有业务ID的新记录,并使用Hibernate entityManager插入它。然而,有多个进程可以同时进行这样的插入。Hibernate的save
方法将插入或更新(如果记录存在于相同的主键)。
所以,如果多个进程正在执行相当于:
进程1:
MySimple r=new MySimple(1,'tagP1')
mySinmpleRepository.save(r);
进程2:
MySimple r=new MySimple(1,'tagP2')
mySinmpleRepository.save(r);
我需要实现一种乐观锁定的形式,并在记录已存在时使更新失败。
不幸的是,到目前为止发生的情况是,记录被更新,并且如果我尝试“插入”一个分离的实体,则没有任何保存方法失败。
示例实体:
create table MySimple(
SIMPLE_ID INTEGER NOT NULL,
TAG VARCHAR(200),
primary key PK_MYSIMPLE (SIMPLE_ID)
)
实体:
@Entity
public class MySimple {
@Id
@Column(name='SIMPLE_ID', nullable=false, updateable=false)
private simpleId;
@Column(name='TAG', nullable=false, updateable=true)
private tag;
// 获取器/设置器/等..
}
public interface MySimpleRepository extends JpaRepository<MySimple, Integer>{
}
英文:
I have this scenario.
I have a web/rest service that receives a new record with a business id and inserts it using hibernate entityManager. However, there are multiple processes which can be doing such an insertion concurrently. Hibernate save
method will insert or update( if the record exists for the same primary key).
so if multiple processes are performing the equivalent of:
process1:
MySimple r=new MySimple(1,'tagP1')
mySinmpleRepository.save(r);
process2:
MySimple r=new MySimple(1,'tagP2')
mySinmpleRepository.save(r);
What i need to achieve is a form of optimistic locking and to fail the update if the record already exists.
Unfortunately what happens so far is that the record gets updated and none of the save methods fail even if I try to "insert" a detached entity
sample entity:
create table MySimple(
SIMPLE_ID INTEGER NOT NULL,
TAG VARCHAR(200),
primary key PK_MYSIMPLE (SIMPLE_ID)
)
entity:
@Entity
public class MySimple {
@Id
@Column(name="SIMPLE_ID", nullable=false, updateable=false)
private simpleId;
@Column(name="TAG", nullable=false, updateable=true)
private tag;
// getters/setters/etc..
}
public interface MySimpleRepository extends JpaRepository<MySimple, Integer>{
}
答案1
得分: 1
这是spring-data-jpa
的设计方式:因为您的实体具有外部标识符,spring-data-jpa
从不将其视为新实体,而是调用em.save()
(在较新版本中为em.merge()
)而不是调用em.persist()
,这将导致Hibernate决定如何处理实体:插入或更新。
选项如下:
-
利用数据库锁,在进入关键部分时锁定数据库中的某些内容并在保存实体之前执行一些检查。在PostgreSQL中,可以使用咨询锁,在其他数据库中,您可以创建一个包含单行并将其锁定的表。
-
为您的实体定义一个“技术”
@Id
字段,并使用提供的“业务标识符”作为@NaturalId
,在这种情况下,唯一约束将有助于解决问题。 -
不要针对此特定情况使用
spring-data-jpa
存储库 - 而是使用EnitytManager
API。 -
利用复合存储库并调用
em.persist()
。 -
向
spring-data-jpa
项目提交CR(代码审查请求),要求提供persist
方法或其他选项,例如boolean isNew
参数。 -
利用@Version - Hibernate(和
spring-data-jpa
)认为具有null
版本的实体是新的。 -
尝试以某种方式在您的实体中实现
org.springframework.data.domain.Persistable
,以告诉spring-data-jpa
在特定情况下应将实体视为新的,例如:
@Entity
public class MySimple extends AbstractPersistable<Integer> {
private Boolean forceNew;
@Transient
@Override
public boolean isNew() {
if (forceNew != null) {
return forceNew;
}
return super.isNew();
}
}
英文:
That is how spring-data-jpa
designed: since your entity has external identifier, spring-data-jpa
never considers it as a new entity, and instead of calling em.persist()
it calls em.save()
(em.merge()
in recent versions), which in turn cause Hibernate to make a decision what to do with entity: insert or update:
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
The options are following:
-
take advantage of DB locks, i.e. lock something in DB upon entering critical section and perform some checks before saving entity. In PostgreSQL it is possible to use advisory locks, in case of other DBs you may create a table with single row and lock it.
-
define a "technical"
@Id
field for your entity, and use "business identifier" provided as@NaturalId
, in that case unique constraint will help -
don't use
spring-data-jpa
repository for this particular case - useEnitytManager
API instead. -
take advantage of Composite Repositories and call
em.persist()
-
file a CR to
spring-data-jpa
project, asking to providepersist
method or some other option,boolean isNew
parameter, for exmaple -
take advantage of @Version - hibernate (and
spring-data-jpa
) believes entities withnull
version are new. -
somehow try to implement
org.springframework.data.domain.Persistable
in your entity in order to tellspring-data-jpa
that in particular scenario it should consider entity as new, for example:
@Entity
public class MySimple extends AbstractPersistable<Integer> {
private Boolean forceNew;
@Transient
@Override
public boolean isNew() {
if (forceNew != null) {
return forceNew;
}
return super.isNew();
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论