JPA悲观锁不起作用

6
我正在使用Spring Boot、JPA、Oracle 12C和下面的Typed Query来选择“NEW”项目进行处理。一旦我选择了一个“NEW”项目,我会更新它的状态,使其不再有资格被选择,但我发现同样的项目出现了并发问题。
我在这里读到,我需要在查询上设置“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;
}
2个回答

4
我可以确认这个观察结果。我已经用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 日志:

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和UPDATE”并提交它们时会出现一种锁定异常。例如,这是H2数据库的行为。 - Jörg Vollmer
这个问题有任何更新吗?你能够解决这个问题或者绕过它了吗? - Anly

1
据我所知,你无法在Oracle中阻止“读取”...悲观锁对应于select for update,它不会阻止其他select语句...它只强制读取数据的旧版本(在select for update运行之前)... 它仅会阻止其他select for update语句(因此其他查询具有悲观锁)。

谢谢您的回复,那解决方案是更新我的查询以使用 select for update 吗? - Orby
让我们确保我们在同一条线上……您现在正在谈论另一个查询,它能够读取数据,但您不希望它这样做……对吗?如果是这样的话……我认为这取决于您的业务和实施……是否需要对其进行锁定?它被调用的频率有多高?这将如何影响性能?存在死锁的机会吗?从业务角度来看,是否允许阻止此查询? - osama yaccoub
我在谈论另一个线程尝试使用上述查询同时更新同一行。我正在云环境中运行应用程序,需要在将状态从“NEW”更改为“IN_PROGRESS”时锁定该行,以便其他线程不能同时读写同一行。 - Orby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接