java - spring默认@Transactional和默认丢失更新

标签 java spring hibernate jpa

spring环境有一个大现象还是我大错特错。 但是默认的 spring @Transactional 注释不是 ACID 而只是缺乏隔离的 ACD。这意味着如果你有方法:

@Transactional
public TheEntity updateEntity(TheEntity ent){
  TheEntity storedEntity = loadEntity(ent.getId());
  storedEntity.setData(ent.getData);
  return saveEntity(storedEntity);
}

如果 2 个线程以不同的计划更新进入,会发生什么情况。他们都从数据库加载实体,他们都应用自己的更改,然后第一个被保存并提交,当第二个被保存并提交时,第一个 UPDATE IS LOST。真的是这样吗?使用调试器,它就是这样工作的。

最佳答案

丢失数据?

您不会丢失数据。可以将其视为更改代码中的变量。

int i = 0;
i = 5;
i = 10;

你“丢”了 5 吗?好吧,不,你替换了它。

现在,您提到的多线程的棘手部分是,如果这两个 SQL 更新同时发生怎么办?

从纯粹的更新角度(忘记读取)来看,这没什么不同。数据库将使用锁来序列化更新,因此一个仍然会在另一个之前进行。第二个自然获胜。

但是,这里有一个危险......

根据当前状态更新

如果更新是基于当前状态的有条件的怎么办?

public void updateEntity(UUID entityId) {
    Entity blah = getCurrentState(entityId);
    blah.setNumberOfUpdates(blah.getNumberOfUpdates() + 1);
    blah.save();
}

现在您遇到了数据丢失的问题,因为如果两个并发线程执行读取 (getCurrentState),它们将各自加 1,得到相同的数字,并且第二次更新将丢失前一次的增量。

解决

有两种解决方案。

  1. Serializable 隔离级别 - 在大多数隔离级别中,读取(select)不持有任何独占锁,因此不会阻塞,无论它们是否在事务中。 Serializable 实际上会为读取的每一行获取并持有独占锁,并且仅在事务提交或回滚时释放这些锁。
  2. 在一条语句中执行更新。 - 单个 UPDATE 语句应该使我们成为原子,即 UPDATE entity SET number_of_updates = number_of_updates + 1 WHERE entity_id = ?

一般来说,后者的可扩展性要高得多。您持有的锁越多,持有的时间越长,您获得的阻塞就越多,因此吞吐量就越低。

关于java - spring默认@Transactional和默认丢失更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49365965/

相关文章:

spring - 如何将@Value 定义为可选

java - 在 servlet 'DispatcherServlet' : using default 中找不到 HandlerMappings

java - Java中策略模式的简洁表达

java - 无法通过 Android 应用程序正确加载 Facebook 页面

java - Tomcat 更新后 WebsocketServerContainer 不在 ServletContext 中

java - hibernate 外键问题: Error executing DDL "alter table..."

hibernate - java.lang.NoSuchFieldError : TRACE while using hibernate 4. 1.8.Final 版本

java - hibernate 多个 @OneToMany 关系在父关系中生成更多结果

java - 如何回滚应用程序引擎部署

java - c :\Program Files\Java\glassfish 4\glassfish\domains\domain1 Not writable