Hibernate保证在并发插入现有记录时失败,而不是更新。

huangapple go评论78阅读模式
英文:

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,&#39;tagP1&#39;)
    mySinmpleRepository.save(r);

process2:


    MySimple r=new MySimple(1,&#39;tagP2&#39;)
    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=&quot;SIMPLE_ID&quot;, nullable=false, updateable=false)
     private simpleId;
    
     @Column(name=&quot;TAG&quot;, nullable=false, updateable=true)
     private tag;
     // getters/setters/etc..
    }

    public interface MySimpleRepository extends JpaRepository&lt;MySimple, Integer&gt;{
    }

答案1

得分: 1

这是spring-data-jpa的设计方式:因为您的实体具有外部标识符,spring-data-jpa从不将其视为新实体,而是调用em.save()(在较新版本中为em.merge())而不是调用em.persist(),这将导致Hibernate决定如何处理实体:插入或更新。

选项如下:

  1. 利用数据库锁,在进入关键部分时锁定数据库中的某些内容并在保存实体之前执行一些检查。在PostgreSQL中,可以使用咨询锁,在其他数据库中,您可以创建一个包含单行并将其锁定的表。

  2. 为您的实体定义一个“技术” @Id 字段,并使用提供的“业务标识符”作为 @NaturalId,在这种情况下,唯一约束将有助于解决问题。

  3. 不要针对此特定情况使用spring-data-jpa存储库 - 而是使用EnitytManager API。

  4. 利用复合存储库并调用em.persist()

  5. spring-data-jpa项目提交CR(代码审查请求),要求提供persist 方法或其他选项,例如 boolean isNew 参数。

  6. 利用@Version - Hibernate(和spring-data-jpa)认为具有null版本的实体是新的。

  7. 尝试以某种方式在您的实体中实现org.springframework.data.domain.Persistable,以告诉spring-data-jpa在特定情况下应将实体视为新的,例如:

@Entity
public class MySimple extends AbstractPersistable&lt;Integer&gt; {

    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 &lt;S extends T&gt; S save(S entity) {

    Assert.notNull(entity, &quot;Entity must not be null&quot;);

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

The options are following:

  1. 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.

  2. define a "technical" @Id field for your entity, and use "business identifier" provided as @NaturalId, in that case unique constraint will help

  3. don't use spring-data-jpa repository for this particular case - use EnitytManager API instead.

  4. take advantage of Composite Repositories and call em.persist()

  5. file a CR to spring-data-jpa project, asking to provide persist method or some other option, boolean isNew parameter, for exmaple

  6. take advantage of @Version - hibernate (and spring-data-jpa) believes entities with null version are new.

  7. somehow try to implement org.springframework.data.domain.Persistable in your entity in order to tell spring-data-jpa that in particular scenario it should consider entity as new, for example:

@Entity
public class MySimple extends AbstractPersistable&lt;Integer&gt; {

    private Boolean forceNew;

    @Transient
    @Override
    public boolean isNew() {
        if (forceNew != null) {
            return forceNew;
        }
        return super.isNew();
    }

}

huangapple
  • 本文由 发表于 2023年5月11日 07:43:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76223253.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定