JPA悲观锁定不起作用

时间:2018-02-20 12:24:16

标签: oracle hibernate jpa transactions locking

我使用Spring Boot,JPA,Oracle 12C和下面的类型查询来选择“' NEW'要处理的项目。一旦我选择了新的' item,我更新了它的状态,因此它不再有资格进行选择,但我发现并发问题,相同的项目被选中。

我看了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;
}

2 个答案:

答案 0 :(得分:2)

我可以证实这一观察。我已经使用H2-Database测试了几种锁定模式,并且所有锁定模式均按预期工作。两种悲观的锁定模式都无法与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=?

答案 1 :(得分:1)

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