1

我有2个并发线程同时输入(Spring)事务服务。删除实体时避免StaleObjectStateException

使用Hibernate,该服务方法加载一些实体,处理这些实体,找到一个并从数据库中删除它。伪代码如下:

@Transactional 
public MyEntity getAndDelete(String prop) { 
    List<MyEntity> list = (List<MyEntity>)sessionFactory 
     .getCurrentSession() 
     .createCriteria(MyEntity.class) 
     .add(Restrictions.eq("prop", prop)) 
     .list(); 

    // process the list, and find one entity 
    MyEntity entity = findEntity(list); 
    if (entity != null) { 
     sessionFactory.getCurrentSession().delete(entity); 
    } 
    return entity; 
} 

如果在同一时间两个线程传递相同的参数,都将“发现”同一实体的两个将调用delete。其中一人在会议结束时将失败投掷org.hibernate.StaleObjectStateException

我想这两个线程都会返回实体,不会抛出异常。为了实现这一点,我试图锁定(与“SELECT ... FOR UPDATE”),删除它之前的实体,如下:

@Transactional 
public MyEntity getAndDelete(String prop) { 
    List<MyEntity> list = (List<MyEntity>)sessionFactory 
     .getCurrentSession() 
     .createCriteria(MyEntity.class) 
     .add(Restrictions.eq("prop", prop)) 
     .list(); 

    // process the list, and find one entity 
    MyEntity entity = findEntity(list); 
    if (entity != null) { 
     // reload the entity with "select ...for update" 
     // to ensure the exception is not thrown 
     MyEntity locked = (MyEntity)sessionFactory 
      .getCurrentSession() 
      .load(MyEntity.class, entity.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE)); 
     if (locked != null) { 
      sessionFactory.getCurrentSession().delete(locked); 
     } 
    } 
    return entity; 
} 

我用load()代替get()根据休眠的API以来,如果已经在会话中,get会返回实体,而加载应该重新读取它。

如果两个线程同时进入上述方法,则其中一个线程阻塞锁定阶段,并且当第一个线程关闭事务时,第二个线程会唤醒org.hibernate.StaleObjectStateException。为什么?

为什么锁定的负载不只是返回null?我怎么能做到这一点?

回答

1

我花了一些时间调查这个问题,我终于明白发生了什么。

PESSIMISTIC_WRITE锁尝试“锁定”已在会话中加载的实体,它不会从数据库重新读取对象。调试通话,我看到entity == locked返回true(以Java术语)。这两个变量都指向同一个实例。

要强制hibernate重新加载实体,必须首先从会话中删除该实体。

下面的代码做的伎俩:

@Transactional 
public MyEntity getAndDelete(String prop) { 
    List<MyEntity> list = (List<MyEntity>)sessionFactory 
     .getCurrentSession() 
     .createCriteria(MyEntity.class) 
     .add(Restrictions.eq("prop", prop)) 
     .list(); 

    // process the list, and find one entity 
    MyEntity entity = findEntity(list); 
    if (entity != null) { 

     // Remove the entity from the session. 
     sessionFactory.getCurrentSession().evict(entity); 

     // reload the entity with "select ...for update" 
     MyEntity locked = (MyEntity)sessionFactory 
      .getCurrentSession() 
      .get(MyEntity.class, entity.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE)); 
     if (locked != null) { 
      sessionFactory.getCurrentSession().delete(locked); 
     } 
    } 
    return entity; 
} 

的PESSIMISTIC_WRITE MUT与get代替load使用,因为否则org.hibernate.ObjectNotFoundException会被抛出。

+0

Session get现已弃用。你知道如何在没有它的情况下重新加载吗? – Alison