【问题标题】:Is it possible to turn off hibernate version increment for particular update?是否可以关闭特定更新的休眠版本增量?
【发布时间】:2016-03-02 13:14:44
【问题描述】:

是否可以在不使用休眠修改实体版本的情况下更新数据库中的实体?

使用我的网络应用程序,用户可以创建或更新实体。在任何用户操作之后“处理”这些实体的另一个异步进程在哪里。如果用户在“处理”实体之前打开实体进行更新,但在“处理”后尝试保存它,用户将得到“OptimisticLockException”,并且他输入的所有数据都将丢失。但我想用用户提供的数据覆盖在异步过程中更新的数据。

代码片段来演示为什么我需要这种行为(JPA + Hibernate):

//user creates entity by filling form in web application
Entity entity = new Entity ();
entity.setValue("some value");
entity.setProcessed (false);
em.persist(entity);
em.flush();
em.clear();

//but after short period of time user changes his mind and again opens entity for update
entity = em.find(Entity.class, entity.getId());
em.clear(); //em.clear just for test purposes

//another application asynchronously updates entities 
List entities = em.createQuery(
                "select e from Entity e where e.processed = false")
                .getResultList();
for (Object o: entities){
    Entity entityDb = (Entity)o;
    someTimeConsumingProcessingOfEntityFields(entityDb); //update lots of diferent entity fields
    entityDb.setProcessed(true);
    em.persist(entityDb);
}        
em.flush(); //version of all processed entities are incremented.       
//Is it possible to prevent version increment?
em.clear();  

//user modifies entity in web application and again press "save" button
em.merge(entity); //em.merge just for test purposes
entity.setValue("some other value");
entity.setProcessed (false);
em.persist(entityDb);
em.flush(); //OptimisticLockException will occur.        
//Is it possible to prevent this exception from happening?
//I would like to overwrite data updated in asynchronous process 
//with user provided data.

还有我的实体:

@Entity
@Table(name = "enities")
public class Entity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Version
    private int versionNum;
    @Column
    private String value
    @Column
    private boolean processed;
    //… and so on (lots other properties)
}

实际上我有更多的类有类似的问题 - 所以我正在寻找一些优雅的非侵入性解决方案。

在我看来,这是很常见的情况。但是我找不到任何信息如何实现这样的功能。

【问题讨论】:

    标签: hibernate jpa


    【解决方案1】:

    版本将始终递增(除非您使用 JPQL)。版本的想法是跟踪更改,并且在 JPA 的情况下还启用乐观锁定,以便应用程序可以与相同的实体同时工作。

    但要解决此问题,您可以从数据库中读取最新版本并应用用户处理过的实体版本中的所有值。

    类似这样的:

    void updateWithUserInput(Entity userVersion) {
        Entity dbVersion = entityManager.find(Entity.class, userVersion.getId);
        // apply changes...
        dbVersion.setX(userVersion.getX());
        // ...and so on
        // Also: We're dealing with an attached entity so a merge call should not be necessary
    }
    

    我假设您确实意识到这会丢弃异步调用执行的任何更改,从而使其过时,因此您也不能这样做并且不需要覆盖任何更改。 :)

    【讨论】:

    • 此解决方案将完全关闭乐观锁定检查,然后多个用户使用 Web 界面编辑同一实体,但我不希望这种情况发生。问题不是然后用户更新实体,而是异步过程更新实体。所以我想我需要以某种方式修改异步过程中的实体更新。
    • 您还可以将实体映射到没有版本属性的不同类,并仅在异步调用中使用它。
    • 是的,这可行,但不幸的是,这需要大量额外的重构(完全重新设计我的域类)。在我的应用程序中,所有域类(超过 100 个类)都使用字段 versionNum 扩展了超类,其中很大一部分通过一些异步进程进行了更新。因此,出于这个特殊原因更改域将是非常“侵入性”的解决方案。在那种情况下,我认为在某些情况下覆盖休眠 DefaultFlushEntityEventListener 类可能会更容易以打开版本增量(但这意味着“分支”休眠)。
    • @Palladium,在这一点上,我只能鼓励您将乐观锁定视为您的应用程序在发生并发修改时必须处理的事情。如果用户想要在同一个实体上工作,也许你可以中止你的异步调用。毕竟只能有一个。
    【解决方案2】:

    您可以通过发出本机查询更新以进行异步处理来绕过 Hibernate 乐观锁定机制:

    em
        .createNativeQuery("update entities set processed = :1 where id =:2")
        .setParameter(1, true)
        .setParameter(2, entityDb.getId())
        .executeUpdate();
    

    【讨论】:

    • 方法 someTimeConsumingProcessingOfEntityFields(entityDb) 更新类 Entity 的多个复杂字段,因此使用本机查询复制所有这些代码将非常困难。 (实际上我有更多类似问题的课程 - 所以我正在寻找一些优雅的非侵入性解决方案)。第二个问题是在休眠时使用本机查询,它会使域对象处于“不一致”状态。
    • 在休眠中使用本机查询的第二个问题:它使域对象处于“不一致”状态(在本机查询直接在 DB 休眠会话中更新域对象后,将包含不同的对象值)。这可能是代码可重用性的问题(您将无法将当前对象传递给其他方法 - 首先您需要从 DB 更新它们)
    【解决方案3】:

    Hibernate Optimistic Locking 可以使用 hibernate Session (http://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/Session.html) replicate(...) 方法绕过。

    不增加版本的代码示例:

    //Detaching to prevent hibernate to spot dirty fields. 
    //Otherwise if entity exists in hibernate session replication will be skipped 
    //and on flush entity will be updated with version increment.
    em.detach(entityDb); 
    someTimeConsumingProcessingOfEntityFields(entityDb);  
    
    //Telling hibernate to save without any version modifications.
    //Update hapends only if no newer version exists.
    //Executes additional query DB to get current version of entity.
    hibernateSession.replicate(entity, ReplicationMode.LATEST_VERSION);
    

    我认为这个方案比原生 SQL 更新更好,因为:

    • ORM 映射(注解或 *.hbm.xml)被使用(无需复制 Java 对象 本地查询中的 DB 表映射);
    • 无需手动执行flush(性能);
    • db 和 hibernate 缓存处于相同状态,无需从缓存中驱逐实体(性能);
    • 而且您仍然拥有 ORM 提供的所有功能,例如乐观锁定等...;

    【讨论】:

    • 如何使用eclipselink实现同样的效果?任何答案here
    猜你喜欢
    • 2017-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多