Hibernate在MySQL

时间:2016-01-04 10:01:31

标签: java mysql spring hibernate locking

我在函数中使用Java(Servlet)中的以下代码(用@Transactional(rollbackFor = Exception.class)包裹:

ModelAccount account = hibernateSession.get(**Class<T> type**, **int id**, new LockOptions(LockMode.PESSIMISTIC_WRITE));

当我通过2个不同的线程同时运行此函数时,我注意到通过此行的第一个线程获取(例如,account.balance = 100)。第二个线程挂起,直到第一个提交。在我的例子中,第一个线程是将余额更新为200。 当锁定的线程恢复时,它仍然会读取account.balance = 100而不是新值。

  • 我在mysql上的隔离级别:

    SHOW GLOBAL VARIABLES LIKE 'tx_isolation';
    Variable_name   Value
    tx_isolation    REPEATABLE-READ
    
  • 在休眠时没有指定隔离。

修改 如果我手动锁定帐户的行,即使隔离级别在数据库级别和休眠级别@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)

上处于READ-COMMITED状态,hibernate代码仍然会起作用。

最后一件事,当执行运行时,我在日志中注意到以下内容:

Hibernate: select id from accounts where id =? for update

这清楚地表明hibernate没有获取帐户的全部细节,而是在使用他之前拥有的数据时发出锁定它的命令。

2 个答案:

答案 0 :(得分:2)

从技术上讲,这不是脏数据。脏读只能在READ_UNCOMMITTED上进行,所以你在这里看到的是可重复的读取,而你想要的是不可重复的读取。

PESSIMISTIC_WRITE对所选数据库行进行独占锁定,因此在第一个事务结束(提交或回滚)之前,没有其他线程可以选择此记录。

第二个交易看到100的值的原因是REPEATABLE_READ isolation level。 InnoDB使用MVCC,在REPEATABLE_READ下,它保证您可以看到记录,就像交易开始时一样。

如果要读取最新值,则需要切换到READ_COMMITTED。

但即使在READ_COMMITTED中,如果Hibernate已经在第一级缓存中缓存了该实体,您仍可能获得100的值。 Hibernate提供application-level repeatable reads,因此一旦加载实体,无论您尝试加载多少次实体引用,都会得到相同的实体引用。

更新

确保@Transactional isolation level is taken into consideration。如果您使用的是JTA,则可能会被忽略。

如果Hibernate以前加载了实体,除非你发出refresh,否则它不会重新加载它。

ModelAccount account = hibernateSession.refresh(ModelAccount.class, id, new LockOptions(LockMode.PESSIMISTIC_WRITE));

答案 1 :(得分:0)

如果您使用的是@Transactional注释,请确保您拥有正确的传播策略。请查看文档中的第16.5.7节:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

您可以通过指定REQUIRES_NEW传播策略来解决此问题。