【问题标题】:JPA Pessimistic Lock Not WorkingJPA悲观锁不工作
【发布时间】:2018-07-30 19:32:58
【问题描述】:

我正在使用 Spring Boot、JPA、Oracle 12C 和下面的类型化查询来选择要处理的“新”项目。选择“新”项目后,我会更新其状态,使其不再符合选择条件,但我发现相同项目被拾取时出现并发问题。

我读到here,我需要在查询中设置“LockModeType.PESSIMISTIC_WRITE”以防止其他线程选择同一行,但它似乎不起作用。

我是否遗漏了以下内容,或者我是否需要其他配置来防止并发线程从我的表中检索相同的行?问题与锁定级别或实体管理器没有更新/刷新有关吗?

我的@Transactional服务:

@Override
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor=RuntimeException.class)
public MyObject retrieveItemByStatus(StatusEnum status) {
    return myRepository.retrieveItemByStatus(status);
}

我的存储库层中的查询:

@Override
public MyObject retrieveItemByStatus(StatusEnum status) {

    String sql = "SELECT t FROM myTable t WHERE status = :status ORDER BY id ASC";      
    try {
        TypedQuery<MyObject> query = em.createQuery(sql, MyObject.class).setParameter("status", status);
        query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
        query.setFirstResult(0);
        query.setMaxResults(1);
        MyObject myObject = (MyObject) query.getSingleResult();
        if (myObject != null) {
            myObject.setStatus(StatusEnum.IN_PROGRESS);
            MyObject myUpdatedObject = em.merge(myObject);                              
            return myUpdatedObject;
        }
    } catch (IllegalArgumentException iae) {
        //some logging
    } catch(NoResultException nrf) {            
        //some logging
    } catch(Exception ex) {
        //some logging
    }       
    return null;
}

【问题讨论】:

    标签: oracle hibernate jpa transactions locking


    【解决方案1】:

    我可以证实这一观察。我用 H2 数据库测试了几种锁定模式,并且都按预期工作。这两种悲观锁定模式都不能与 Oracle 数据库一起正常工作。因此,问题是:这段代码有什么问题?

    对于 Oracle,其中两个并发代码执行产生相同的数据,尽管第一个应该阻塞第二个:

    // Every Thread gets its own Hibernate session:
    final Session session = HibernateSessionHolder.get();
    
    session.getTransaction().begin();
    final List<EventDeliveryDataDB> eddList = 
            session.createCriteria(EventDeliveryDataDB.class)
                .setLockMode(LockMode.PESSIMISTIC_WRITE) // with READ the same
                .add(eq("progress", NEW))
                .list();
    eddList.stream().forEach(eddElem -> eddElem.setProgress(IN_PROGRESS));
    session.getTransaction().commit();
    

    休眠日志:

    Hibernate: select this_.DD_ID as DD_ID1_2_0_, this_.CHANNEL_NAME as CHANNEL_NAME2_2_0_, this_.created as created3_2_0_, this_.DELIVERY_TIME as DELIVERY_TIME4_2_0_, this_.ERROR_CODE as ERROR_CODE5_2_0_, this_.ERROR_MESSAGE as ERROR_MESSAGE6_2_0_, this_.EVENT_ID as EVENT_ID7_2_0_, this_.MAX_RETRIES as MAX_RETRIES8_2_0_, this_.PROGRESS as PROGRESS9_2_0_, this_.PROGRESS_ID as PROGRESS_ID10_2_0_, this_.RECIPIENT_CRID as RECIPIENT_CRID11_2_0_, this_.RETRY_COUNTER as RETRY_COUNTER12_2_0_, this_.RETRY_TIME as RETRY_TIME13_2_0_, this_.updated as updated14_2_0_ from HR.NOS_DELIVERY_DATA this_ where this_.PROGRESS=?
    Hibernate: select this_.DD_ID as DD_ID1_2_0_, this_.CHANNEL_NAME as CHANNEL_NAME2_2_0_, this_.created as created3_2_0_, this_.DELIVERY_TIME as DELIVERY_TIME4_2_0_, this_.ERROR_CODE as ERROR_CODE5_2_0_, this_.ERROR_MESSAGE as ERROR_MESSAGE6_2_0_, this_.EVENT_ID as EVENT_ID7_2_0_, this_.MAX_RETRIES as MAX_RETRIES8_2_0_, this_.PROGRESS as PROGRESS9_2_0_, this_.PROGRESS_ID as PROGRESS_ID10_2_0_, this_.RECIPIENT_CRID as RECIPIENT_CRID11_2_0_, this_.RETRY_COUNTER as RETRY_COUNTER12_2_0_, this_.RETRY_TIME as RETRY_TIME13_2_0_, this_.updated as updated14_2_0_ from HR.NOS_DELIVERY_DATA this_ where this_.PROGRESS=?
    Hibernate: select DD_ID from HR.NOS_DELIVERY_DATA where DD_ID =? for update
    Hibernate: select DD_ID from HR.NOS_DELIVERY_DATA where DD_ID =? for update
    Hibernate: update HR.NOS_DELIVERY_DATA set CHANNEL_NAME=?, created=?, DELIVERY_TIME=?, ERROR_CODE=?, ERROR_MESSAGE=?, EVENT_ID=?, MAX_RETRIES=?, PROGRESS=?, PROGRESS_ID=?, RECIPIENT_CRID=?, RETRY_COUNTER=?, RETRY_TIME=?, updated=? where DD_ID=?
    Hibernate: update HR.NOS_DELIVERY_DATA set CHANNEL_NAME=?, created=?, DELIVERY_TIME=?, ERROR_CODE=?, ERROR_MESSAGE=?, EVENT_ID=?, MAX_RETRIES=?, PROGRESS=?, PROGRESS_ID=?, RECIPIENT_CRID=?, RETRY_COUNTER=?, RETRY_TIME=?, updated=? where DD_ID=?
    

    【讨论】:

    • 附加说明:我希望第二次调用在尝试执行“SELECT FOR UPDATE and UPDATE”并提交它们时应该以某种 LockingException 结束。这就是 H2 数据库的行为。
    • 关于这个问题的任何更新?您是否能够解决或解决此问题?
    【解决方案2】:

    AFAIK 你不能在 oracle 中阻止“读取”...悲观锁对应select for update,它不会阻止其他select 语句...它只强制它读取旧版本的数据(之前select for update 运行)...它只会阻塞其他 select for update 语句(因此其他查询具有悲观锁)

    【讨论】:

    • 感谢您的回复,有什么解决方案更新我的查询以使用select for update
    • 让我们确保我们在同一行...您现在谈论的是能够读取数据但您不希望它这样做的另一个查询...对吗?如果是这种情况..我认为这取决于您的业务和实施...是吗。需要锁定吗?它多久调用一次?这将如何影响性能?有没有死锁的机会?是否允许商业明智地阻止此查询?
    • 我说的是另一个线程尝试使用上述查询同时更新同一行。我在云环境中运行应用程序,当我将状态从NEW 更新为IN_PROGESS 时需要锁定行,因此没有其他线程可以同时读取/写入同一行。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多