Spring @Transactional with synchronized 关键字不起作用

Spring @Transactional with synchronized keyword doesn't work

假设我有一个 java class 使用这样的方法(只是一个例子)

@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'。但奇怪的是,它在

的第二次调用中失败了

"Cannot add book - book already exist".

我花了很多时间试图解决这个问题,所以我想分享答案。

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

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

T2:|--------添加图书------>

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

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

@Transactional 注释是一个方面,它的作用是在您的 class,在方法调用之前添加一些代码(开始事务),调用该方法然后调用一些其他代码(提交事务 ).所以第一个线程现在看起来像这样:

T1:|--Spring 开始交易--|-----删除书籍----- |--Spring 提交交易--->

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

第二个线程是这样的:

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

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

请注意,@Transactional 注释仅锁定数据库中的同一实体,以免被同时修改。由于我们要添加一个不同的实体(但书名相同),所以效果不大。但它仍然不应该受到伤害,对吗?

这是有趣的部分:

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

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

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

所以。当"add"方法读取数据库时,remove代码已经是运行,但不是commit代码,所以它仍然在数据库中找到对象并抛出错误。几毫秒后它将从数据库中消失。

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

您需要设置事务隔离级别以防止从数据库中进行脏读,而不用担心线程安全。

@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

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

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