【问题标题】:JPA two transaction updating the same rowsJPA 两个事务更新相同的行
【发布时间】:2018-01-14 14:27:53
【问题描述】:

我在我的 SpringBoot 应用程序中使用 JPA 存储库我有一个后台任务,它定期将我的数据库中的某些元素设置为“就绪” 我还有一个用户可以调用的端点,它可以修改同一个表的行之一。

有没有办法避免一个又一个取消对方的写入?以这种情况为例

Table: 
Key
id     name      is_ready

0) 初始数据有一个键(1 no_name false)

1)后台任务启动,即将通过设置修改表Key

is_ready to true
Key key = repo.findKeyByIsReady(false)
key.setIsReady(true)
repo.save(key) <--- does NOT yet execute this

2) 用户调用 api 端点将键名更改为“new_name”并完成

3) 现在后台服务执行repo.save(key),最终数据为

1    no_name   true 

而不是

1    new_name   true

基本上后台任务已经覆盖了用户设置的键名

有没有办法避免这种情况?交易在这里有什么帮助?

【问题讨论】:

    标签: java mysql jpa concurrency transactions


    【解决方案1】:

    有几种方法可以解决这个问题 -

    1. 仅更新 is_ready 字段

      • 在您的后台任务中,不要更新记录的所有字段,而是仅更新 is_ready 字段。这样,您就不会覆盖对其他字段的更改
    2. 通过使用具有更严格数据库隔离级别的事务来锁定行 -

      • MySQL 提供all standard database isolation levels
      • 您可以使用“SERIALIZABLE”隔离级别
      • 在此级别下,一旦您启动一个事务并读取一行,在第一个事务提交(或回滚)之前,没有其他事务可以更新同一行
      • 因此,在您的情况下,即使两个进程都设法读取处于相同旧状态的行,也只有其中一个能够成功更新它。另一个进程将无法更新记录(我自己没有对此进行测试,但这就是它在此隔离级别下应该如何工作)
      • 可以像这样使用Spring的@Transactional注解来标记隔离级别——@Transactional(isolation=Isolation.SERIALIZABLE)
      • 请注意,在使用如此严格的隔离级别时必须小心。涉及读取多个表的错误使用可能会导致死锁

    【讨论】:

      【解决方案2】:

      这通常通过额外的锁来解决:

      乐观锁

      您在第二次交易中检测到该行已被其他人更改。然后你要么告诉用户并要求手动修复,要么尝试自动合并更改。

      要实现它,您必须在表格中添加额外的列 - 版本。然后在更新行时,查询将如下所示:

      UPDATE ..., version=old_version+1 WHERE id=old_id and version=old_version
      

      如果WHERE close 没有找到该行(因为其他人增加了版本),则更改的行数将为 0(JDBC 从 DB 获取此信息)并且 JPA 在这种情况下会抛出错误。

      附加字段必须在 JPA 中映射为 @Version

      悲观锁

      每次更新你使用构造的实体:

      select ... for update
      

      当第二个事务发出这样的语句时,数据库锁定该请求直到第一个事务完成。如果这在特定时间段内没有发生,您会从 JPA 获得异常。有关详细信息,请参阅EntityManager#lock() 方法。

      【讨论】:

        猜你喜欢
        • 2011-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-13
        • 1970-01-01
        相关资源
        最近更新 更多