假设我有一个类似这样的方法的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"); } } }
假设这本书被删除,然后重新添加了一个新作者或其他小改动,所以这个方法可能会从另一个系统快速调用两次,首先删除Book,然后再添加相同的Book(带有一些新的细节) ).
为了解决这个问题,我们可能会尝试(像我一样)添加上面的@Transactional代码,然后在@Transactional不起作用时也"同步".但奇怪的是,它在第二次通话中失败了
"无法添加书本已经存在".
我花了很多时间试图解决这个问题,所以我想我会分享答案.
当删除并立即添加一本Book时,如果我们没有"@Transactional"或"synchronized",我们将从这个线程执行开始:
T1:| -----删除书----->
T2:| -------加书------->
该synchronized
关键字确保该方法一次只能由一个线程运行.这意味着执行成为:
T1:| -----删除书-----> T2:| --------添加书------>
该@Transactional
注释是一个方面,而它的作用是,它会创建一个代理在你的类的Java类,添加一些代码(开始事务的方法调用之前)给它,调用该方法,然后调用一些其他的代码(提交事务).所以第一个线程现在看起来像这样:
T1:| --Spring开始事务 - | -----删除书----- | - 弹簧提交事务--->
或更短:T1:| -B- | -R- | -C - >
和第二个线程是这样的:
T2:| - 春天开始交易 - | -------添加书------- | - 春天提交交易--->
T2:| -B- | -A- | -C - >
请注意,@Transactional
注释仅锁定同时修改数据库中的同一实体.由于我们正在添加一个不同的实体(但具有相同的书名),因此它没有多大好处.但它仍然不应该HURT对吗?
在这里是有趣的部分:
Spring添加的事务代码不是synchronized方法的一部分,因此T2线程实际上可以在"提交"代码完成运行之前启动其方法,就在第一个方法调用完成之后.像这样:
T1:| -B- | -R- | -C-- | - >
T2:| -B ------ | -A- | -C - >
所以.当"add"方法读取数据库时,删除代码已经运行,但不是提交代码,因此它仍然在数据库中找到对象并抛出错误.几毫秒后,它将从数据库中消失.
删除@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-隔离,传播