如何使Hibernate的@Lock注解在Oracle数据库中起作用?

4

我在使用Oracle数据库时遇到了锁定行的问题。锁的目的是为了防止多个事务从数据库中读取数据,因为这些数据会影响新数据的生成,并且在事务期间会发生更改。

为了实现锁定,我在SpringData查找方法上放置了@Lock注释,该方法检索参与事务的数据。

@Lock(LockModeType.PESSIMISTIC_WRITE)
User findUserById(@Param("id") String operatorId);

当这段代码被实现后,我会收到日志信息。

org.hibernate.loader.Loader - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes

此外,它没有作用并会导致。
org.springframework.dao.DataIntegrityViolationException: could not execute batch; SQL [insert into ...]

问题可以通过使用实体管理器重写锁来解决。
entityManager.lock(userByIdWithLockOnReadWrite, LockModeType.PESSIMISTIC_WRITE);    

或者

entityManager.unwrap(Session.class).lock(userByIdWithLockOnReadWrite, LockMode.PESSIMISTIC_WRITE);

问题在MariaDB(MySQL)上没有出现。
也许有一些使用注释的特殊规则?
2个回答

14

你说过:

锁的目的是防止多个事务从DB读取数据,因为这些数据会影响新数据的生成并且在事务中会发生改变。

Oracle使用MVCC(多版本并发控制),因此读者不会阻塞写者,写者也不会阻塞读者。即使您使用Oracle获得了行级锁,并且在未提交的情况下修改了该行,其他事务仍然可以读取最后一个提交的值。

与此日志消息相关:

org.hibernate.loader.Loader - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes

这种后续锁定机制是因为Oracle在进行Oracle 11g分页、使用DISTINCTUNION ALL时无法应用锁定。

如果您使用的是Oracle 12i,则可以将Hibernate方言更新为Oracle12cDialect,分页和锁定将正常工作,因为Oracle 12使用SQL标准分页,不再需要派生表查询。

这在MariaDB或任何其他数据库中都不会发生。这只是Oracle 12之前的限制。

如果您正在使用Hibernate 5.2.1,则我们添加了一个新的提示HINT_FOLLOW_ON_LOCKING,可以禁用此机制。

因此,您的Spring Data查询变成:

@QueryHints(value = { @QueryHint(name = "hibernate.query.followOnLocking", value = "false")}, forCounting = false)
@Lock(LockModeType.PESSIMISTIC_WRITE)
User findUserById(@Param("id") String operatorId);

你也可以手动应用它:

User user = entityManager.createQuery(
    "select u from User u where id = :id", User.class)
.setParameter("id", id);
.unwrap( Query.class )
.setLockOptions(
    new LockOptions( LockMode.PESSIMISTIC_WRITE )
        .setFollowOnLocking( false ) )
.getSingleResult();

非常感谢您的回答。关于Follow-on-Locking,我现在已经很清楚了。但据我所知,只要我使用Oracle12cDialect注释就应该可以工作。这就是当问题发生时我所拥有的。我的Hibernate版本是5.0.2.Final。 - Olexandra Dmytrenko
使用Hibernate 5.0,您可以为每个查询获取跟随锁定。无论您使用什么方言都没有关系。只有Hibernate 5.2.1添加了智能跟随锁定的支持。 - Vlad Mihalcea

-1

是否有选项可以使一个事务(TxB)等待一段时间(而不会抛出锁获取异常),以便另一个事务(TxA)释放锁定。


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