【问题标题】:JPA Pessimistic Lock attempt never times outJPA 悲观锁尝试永远不会超时
【发布时间】:2013-10-16 10:58:04
【问题描述】:

我正在尝试在 JPA 中使用悲观锁定,通过 Hibernate 3 针对 Postgres 数据库。我无法让锁超时 - 它似乎永远挂起。

这是一个例子:

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 应该尝试最多 5 秒(5000 毫秒)来获取锁,然后应该抛出异常。相反,代码会陷入僵局。

如果我在两个不同的线程中运行它,那么我会看到 thread2(带有 em2)在 thread1(em1)释放它时立即获得锁。所以锁定正在发生,只是永远不会超时。

我使用 PESSIMISTIC_WRITE 和任何超时值(2ms、0ms 'NO WAIT')等都得到相同的效果。

我正在使用 Hibernate 3.6.10 Final(最新的 Hibernate 3 版本)和 Postgres jdbc 驱动程序 9.2-1003.jdbc4(最新的驱动程序)。我正在运行 Postgres 8.4 数据库。

我找到的所有文档都表明这应该可行。有什么想法吗?

谢谢, 阿拉斯泰尔

【问题讨论】:

    标签: hibernate postgresql jpa pessimistic-locking


    【解决方案1】:

    把它放在你的 persistence.xml 中:

    <property name="javax.persistence.lock.timeout" value="0"/>
    

    或者在调用第一个锁之前设置属性。

    【讨论】:

    • 嗨,Márcio,谢谢,但作为 persistence.xml 变量或第一个锁的属性都没有区别。
    【解决方案2】:

    Postgres SELECT for update 语法仅提供在无法立即获得锁时不等待的选项。请参阅 postgres 文档。

    为了防止操作等待其他事务 提交,使用NOWAIT选项。使用NOWAIT,该语句报告一个 如果无法锁定选定的行,则错误,而不是等待 立即地。请注意,NOWAIT 仅适用于行级锁 — 所需的 ROW SHARE 表级锁仍然是在普通的 方式(见第 13 章)。您可以先将 LOCK 与 NOWAIT 选项一起使用, 如果您需要在不等待的情况下获取表级锁。

    在使用 postgres 时,我观察到任何超过 0 的超时值都会导致休眠发出 SELECT FOR UPDATE,但当超时为 0 时,它会发出 SELECT FOR UPDATE NO WAIT

    【讨论】:

    • 虽然不是我希望的答案,但这似乎与我所看到的一致 - 谢谢!
    • 如果您没有在属性文件中将 javax.persistence.lock.timeout 设置为零,则仅使用“FOR UPDATE”执行查询。如果将其设置为零(如我所说),则执行“FOR UPDATE NOWAIT”。
    • 至少在 Hibernate 4.3.x 和 JPA 2.1 中,您可以在查询级别指定这些设置。在 @NamedQuery(通过提示属性)和通过 entityManager.createQuery(CriteriaBuilder.createQuery(Entity...)) 使用 setHint(...) 创建的查询中。
    • @Lock(value = LockModeType.PESSIMISTIC_WRITE) 对我来说使休眠添加“FOR UPDATE”但设置 props.setProperty("javax.persistence.lock.timeout", "0");不添加NOWAIT。我尝试注入 EntityManager 并查询它也返回 0,em.getProperties().get("javax.persistence.lock.timeout")。我正在尝试原始 sql,但是休眠没有适当地包装异常。
    • @Joel 我发现这是由于使用了方言,如果您使用正确的 Postgres-方言,此属性将被尊重并翻译成 NOWAIT
    【解决方案3】:

    javax.persistence.lock.timeout 在如下提供时也不适合我

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

    但后来我尝试了其他有效的方法。现在我使用实体管理器配置我的 hbernate,而不是使用 @Repository 和 CrudRepository。使用 createQuery 以及锁定和设置锁定超时。并且此配置按预期工作。我有两个并行运行的事务,并试图在数据库中锁定完全相同的行。第一个事务能够获得 WRITE 锁并在释放锁之前持有锁大约 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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-29
      • 1970-01-01
      • 1970-01-01
      • 2017-01-21
      相关资源
      最近更新 更多