2017-08-07 119 views
0

我使用JPA库在我SpringBoot应用程序相同的行我有我的数据库定期设置某些元素“准备好” 我也有一个用户可以调用和端点的后台任务可以修改同一个表的一行。JPA两种事务更新

有没有办法来避免一个又一个相互抵消写?就拿这个场景

Table: 
Key 
id  name  is_ready 

0)有初始数据的关键(1个NO_NAME假)

1)在后台任务踢和即将通过设置

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 

基本上后台任务已覆盖键名用户

是有办法避免这种情况的设置?交易如何在这里有所帮助?

回答

1

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

乐观锁

您检测,该行被别人改变第二交易。然后,您要么告诉用户并要求手动修复或尝试自动合并更改。

要实现它,你不得不额外列添加到表 - 版本。然后更新该行查询时,会是这个样子:

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

如果WHERE接近没有找到行(因为别人递增版本),更改的行数是0(JDBC得到这个来自数据库的信息)和JPA会在这种情况下引发错误。

附加字段将必须被映射为JPA @Version

悲观锁

每次更新的实体使用建筑:

select ... for update 

当第二个交易等问题发言,DB的锁请求,直到第一个事务完成。如果在特定时间内没有发生这种情况,您会从JPA中获得例外。有关更多信息,请参阅EntityManager#lock()方法。

0

有几种方法来解决这个问题 - 而不是更新记录的所有领域

  1. 更新只is_ready场

    • 在你的后台任务,仅更新is_ready领域。

      • MySQL提供all standard database isolation levels
      • 您可以使用“SERIALIZABLE” - 这样的话,你会不会通过使用事务与数据库更加严格的隔离级别覆盖变化等领域
    • 锁行隔离级别

    • 在此级别下,一旦启动事务并读取一行,在第一个事务提交(或回滚)之前,没有其他事务可以更新同一行
    • 因此,在您的情况下,即使两个进程都设法读取相同旧状态中的行,只有其中一个将能够成功更新它。另一个进程将无法更新记录(我没有自己测试过,但它是如何在此隔离级别下工作的)
    • 您可以使用Spring的@Transactional注释来标记隔离级别 - @Transactional(isolation=Isolation.SERIALIZABLE)
    • 请注意,使用这种严格的隔离级别时必须小心。涉及从多个表读取的不正确用法可能导致死锁