JPA悲观锁尝试永远不会超时

8
我正在尝试在Postgres数据库中使用Hibernate 3对JPA进行悲观锁定。但我无法使锁定超时 - 它似乎永远挂起。

以下是一个示例:

EntityManagerFactory factory; 

// (initialise the factory )

EntityManager em1 = factory.createEntityManager();
EntityManager em2 = factory.createEntityManager();

// em1 gets a lock

EntityTransaction transaction1 = em1.getTransaction();
transaction1.begin();
MyObject object1 = em1.find( MyObject.class, 1, LockModeType.PESSIMISTIC_READ );

// em2 tries for a lock

Map<String,Object> timeoutProperties = new HashMap<String,Object>();
timeoutProperties.put("javax.persistence.lock.timeout", 5000);

EntityTransaction transaction2 = em2.getTransaction();
transaction2.begin();
MyObject object2 = em2.find( MyObject.class, 1, LockModeType.PESSIMISTIC_READ, timeoutProperties );

// After five seconds I expect em2 to bail out, but it never does.

transaction1.rollback();
transaction2.rollback();

据我所知,em2应该尝试获取锁并等待最多五秒钟(5000毫秒),然后抛出异常。但实际上,代码却出现了死锁。
如果我在两个不同的线程中运行此代码,则可以看到em2所在的thread2在em1释放锁后立即获取了锁。因此,锁定正在发生,只是从未超时。
我使用了Hibernate 3.6.10 Final(最新的Hibernate 3版本)和Postgres jdbc驱动程序9.2-1003.jdbc4(最新驱动程序)。我针对Postgres 8.4数据库运行。
我找到的所有文档都表明这应该有效。有什么建议吗?
谢谢, Alastair
3个回答

12

PostgreSQL的SELECT FOR UPDATE语法提供了在无法立即获得锁时不等待的选项。详见PostgreSQL文档。

为了防止操作等待其他事务提交,可以使用NOWAIT选项。使用NOWAIT时,如果无法立即锁定所选行,则语句会报告错误而不是等待。请注意,NOWAIT仅适用于行级锁 — 所需的ROW SHARE表级锁仍按常规方式取得(参见第13章)。如果需要在不等待的情况下获取表级锁,可以首先使用LOCK和NOWAIT选项。

在与PostgreSQL一起工作时,我观察到超过0的任何值都会导致Hibernate发出SELECT FOR UPDATE,但当超时为0时,它会发出SELECT FOR UPDATE NO WAIT


虽然这不是我所希望的答案,但似乎与我所见的一致 - 谢谢! - Alastair
1
如果您在属性文件中不将javax.persistence.lock.timeout设置为零,则查询仅使用“FOR UPDATE”执行。如果您将其设置为零(如我所说),则执行“FOR UPDATE NOWAIT”。 - Marcio J
至少在Hibernate 4.3.x和JPA 2.1中,您可以在查询级别上指定这些设置。无论是在@NamedQuery(通过hints属性)还是通过entityManager.createQuery(CriteriaBuilder.createQuery(Entity ...))创建的查询中,都可以使用setHint(...)。 - gkephorus
1
@Lock(value = LockModeType.PESSIMISTIC_WRITE) 对我来说让Hibernate添加了 "FOR UPDATE",但是设置 props.setProperty("javax.persistence.lock.timeout", "0"); 却没有添加 NOWAIT。我尝试注入 EntityManager 并查询它也返回 0,em.getProperties().get("javax.persistence.lock.timeout")。我正在尝试使用原始 SQL,但是 Hibernate 没有适当地包装异常。 - Joel
@Joel,我发现这是由于使用的方言不同造成的,如果您使用正确的Postgres-Dialect,则此属性将被尊重并翻译为“NOWAIT”。 - hotzen
@Joel 我也遇到了同样的问题。我尝试了很多方法,但是无论如何javax.persistence.lock.timeout似乎都不起作用。我有两个事务并行运行。第一个事务获取悲观锁并等待一段时间,然后释放锁。同时,当第二个事务尝试在同一行表上获取锁时,它会抛出错误,表示无法获取锁而不是等待。 - old_soul_on_the_run

0
将以下内容添加到您的 persistence.xml 文件中:
<property name="javax.persistence.lock.timeout" value="0"/>

或在第一次锁定之前设置属性。


嗨,马尔西奥,谢谢,但是无论是作为persistence.xml变量还是第一个锁的属性,都没有任何区别。 - Alastair

0

当我像下面这样提供javax.persistence.lock.timeout时,它似乎也不起作用

@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout",value = "15000")})

但是后来我尝试了另外一种方法,这个方法行得通。我不再使用 @Repository,而是使用 CrudRepository,现在我正在使用实体管理器来配置我的 hibernate。使用 createQuery 以及设置锁定和锁定超时。这个配置按照预期工作。我有两个并行运行的事务,并尝试在数据库中锁定同一行。第一个事务能够获取写锁,并在释放锁前持有锁约10秒。同时,第二个事务尝试锁定同一行,但由于javax.persistence.lock.timeout设置为15秒,它等待锁被释放,然后获得自己的锁。因此使流程串行化。
@Component
public class Repository {

    @PersistenceContext
    private EntityManager em;

    public Optional<Cache> getById(int id){
        List<Cache> list = em.createQuery("select c from Cache c where c.id = ?1")
                            .setParameter(1, id)
                            .setHint("javax.persistence.lock.timeout", 15000)
                            .setLockMode(LockModeType.PESSIMISTIC_WRITE)
                            .getResultList();


        return Optional.ofNullable(list.get(0));
    }

    public void save(Cache cache) {
        cache = em.find(Cache.class, cache.getId());
        em.merge(cache);
    }
}

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