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 这样做,方法执行过程是这样的:保持同步锁,保持事务锁,释放事务锁,释放同步锁。然而,第四步似乎发生在第三步之前。所以修改事务隔离是很有必要的。有人知道原因吗?
假设我有一个 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 这样做,方法执行过程是这样的:保持同步锁,保持事务锁,释放事务锁,释放同步锁。然而,第四步似乎发生在第三步之前。所以修改事务隔离是很有必要的。有人知道原因吗?