1

我有 2 个并发线程同时进入(Spring)事务服务。

使用 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;
}

如果两个线程同时传递相同的参数,则两者都将“找到”两个都将调用的相同实体deleteorg.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 将返回实体,而 load 应该重新读取它。

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

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

4

1 回答 1

1

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

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

要强制休眠重新加载实体,必须先将其从会话中删除。

以下代码可以解决问题:

@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 与代​​替 一起使用getload因为否则org.hibernate.ObjectNotFoundException会抛出 a。

于 2011-10-20T18:30:26.017 回答