【问题标题】:Spring @Transactional with synchronized keyword doesn't work带有 synchronized 关键字的 Spring @Transactional 不起作用
【发布时间】:2017-06-05 16:34:54
【问题描述】:

假设我有一个带有这样方法的 java 类(只是一个例子)

@Transactional
public synchronized void onRequest(Request request) {

    if (request.shouldAddBook()) {
        if (database.getByName(request.getBook().getName()) == null) {
            database.add(request.getBook());
        } else {
            throw new Exception("Cannot add book - book already exist");
        }
    } else if (request.shouldRemoveBook()) {
        if (database.getByName(request.getBook().getName()) != null) {
            removeBook();
        } else {
            throw new Exception("Cannot remove book - book doesn't exist");
        }
    }
}

假设这本书被删除,然后重新添加了一个新作者或其他微小的变化,所以这个方法可能会从另一个系统快速调用两次,首先是删除这本书,然后再添加相同的书(使用一些新的细节)。

为了处理这个问题,我们可能会尝试(像我一样)添加上面的@Transactional 代码,然后在@Transactional 不起作用时也“同步”。但奇怪的是,它在第​​二次调用时失败了

“无法添加图书 - 图书已存在”。

我花了很多时间试图弄清楚这一点,所以我想我会分享答案。

【问题讨论】:

  • 如果你想改变细节,你更新它。您不会删除并添加它。这是可怕的设计。如果你做事正确,你就不需要synchronized
  • 这个问题没有真正找到,因为我想更新一本书的细节。就像你说的那样,那将是糟糕的设计。这只是一个例子。然而,这种模式可能会发生还有其他原因,其他人可能会像我一样遇到这种奇怪的情况,所以我想向其他人展示为什么它不起作用,这样他们就不必花这么长时间来试图理解和我一样。
  • 对我来说同样的问题。例如创建对象时检查重复名称。我用onRequest的同步调用方法解决了这个问题。

标签: java spring synchronized transactional


【解决方案1】:

当删除并立即添加回一本书时,如果我们没有“@Transactional”或“同步”,我们将从这个线程执行开始:

T1: |-----删除书----->

T2: |-------加书-------->

synchronized 关键字确保方法只能由一次一个线程运行。这意味着执行变成了这样:

T1:|-----删除书籍-----> T2:|--------添加书籍------>

@Transactional 注释是一个 Aspect,它的作用是围绕您的类创建 代理 java 类,添加一些代码(begin transaction)它在方法调用之前,调用方法,然后调用其他代码(commit transaction)。所以第一个线程现在看起来像这样:

T1: |--Spring 开始事务--|-----删除书-- |--Spring 提交事务-->

或更短:T1:|-B-|-R-|-C-->

第二个线程是这样的:

T2: |--Spring 开始事务--|-------添加书籍------- |--Spring 提交事务 --->

T2:|-B-|-A-|-C-->

请注意,@Transactional 注释仅锁定数据库中的同一实体,以免被同时修改。由于我们要添加一个不同的实体(但具有相同的书名),它并没有多大用处。但它仍然不应该伤害,对吧?

这里是有趣的部分:

Spring添加的事务代码不是同步方法的一部分,所以T2线程实际上可以在“提交”代码运行完成之前启动它的方法,在第一个方法调用完成之后。像这样:

T1:|-B-|-R-|-C--|-->

T2:|-B------|-A-|-C-->

所以。当“add”方法读取数据库时,删除代码已经运行,但不是提交代码,所以它仍然在数据库中找到对象并抛出错误。毫秒后它会从数据库中消失。

删除@Transactional 注释将使synchronized 关键字按预期工作,尽管这不是其他人提到的一个好的解决方案。删除synchronized 并修复@Transactional 注释是更好的解决方案。

【讨论】:

  • 当然这里真正的问题是synchronized的使用。这不是您想要放入数据访问代码的内容。对于损坏的设计,这本质上是一个糟糕的修复。
  • 如果他删除了@Transactional 注释,他将如何管理事务?即使我同意同步是问题所在,这也不是解决问题的方法。
  • 同意它可能不是“解决问题”更像是“消除你所处的特定情况”。
  • @JavaDevSweden 添加@Transactional(isolation = Isolation.READ_UNCOMMITTED) 会解决这里的问题吗?我也面临着类似的情况
  • @NickDiv 不,我不这么认为,请参阅我对另一个答案的评论。我尝试了所有隔离级别,但都没有解决手头的问题。
【解决方案2】:

你需要设置你的事务隔离级别来防止来自数据库的脏读,不用担心线程安全。

@Transactional(isolation = Isolation.SERIALIZABLE)
public void onRequest(Request request) {

    if (request.shouldAddBook()) {
        if (database.getByName(request.getBook().getName()) == null) {
            database.add(request.getBook());
        } else {
            throw new Exception("Cannot add book - book already exist");
        }
    } else if (request.shouldRemoveBook()) {
        if (database.getByName(request.getBook().getName()) != null) {
            removeBook();
        } else {
            throw new Exception("Cannot remove book - book doesn't exist");
        }
    }
}

这是对事务传播和隔离的一个很好的解释。

Spring @Transactional - isolation, propagation

【讨论】:

  • 数据库不会认为这两个书的对象是不同的并允许同时读写它们吗?
  • 数据库默认不做,但我认为可以在数据库级别配置。这是一个 DBA 的东西,我不是这方面的专家,所以,我考虑在代码级别实现它:)
  • 在尝试了所有隔离级别之后,我可以说(至少在我当前设置的示例中)两个线程总是被允许从数据库中读取值,即使另一个线程最近读取了相同的值而没有提交它的交易。到目前为止唯一有效的是 synchronized 关键字...
  • 使用 SERIALIZABLE 可以防止脏读,但如果两个或多个线程同时访问该方法,您最终可能会遇到异常。有什么办法可以防止这种情况发生吗?错误:由于事务之间的读/写依赖关系,无法序列化访问详细信息:原因代码:在写入过程中,在识别为枢轴时取消。提示:如果重试,事务可能会成功。
  • @Avinash 添加 @Transactional(isolation = Isolation.READ_UNCOMMITTED) 会解决这里的问题吗?我也面临着类似的情况
【解决方案3】:

'synchronized' 应该在@Transaction 方法之前使用。 否则,多线程时,对象已经解锁,但事务没有提交。

【讨论】:

    【解决方案4】:

    我将“同步”添加到 onRequest 的调用方方法,并将 Transactional 的隔离更改为 READ_UNCOMMITTED。这可以解决问题。但我很好奇为什么只将“同步”移动到调用方方法不起作用。因为这样做,方法执行过程如下:保持同步锁,保持事务锁,释放事务锁,释放同步锁。然而,第四步似乎发生在第三步之前。所以修改事务隔离是必要的。谁知道原因?

    【讨论】:

      猜你喜欢
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多