提问人:mjs 提问时间:8/5/2015 最后编辑:Naren Muralimjs 更新时间:1/9/2018 访问量:2065
Spring + Hibernate 手动创建事务,PROPAGATION_REQUIRED失败。错误?
Spring + Hibernate manually creating transactions, PROPAGATION_REQUIRED fails. BUG?
问:
请不要建议我为此使用事务注释。
我遇到了一个似乎与Spring处理事务有关的错误。
请看这两个测试用例,代码中有注释:
实体类,例如:
@Entity
public class Person{
@Id
String name;
}
使用的一些方法:
public TransactionStatus requireTransaction() {
TransactionTemplate template = new TransactionTemplate();
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return getTransactionManager().getTransaction(template);
}
public Session session() {
return getRepository().session();
}
public PlatformTransactionManager getTransactionManager() {
return getRepository().getTransactionManager();
}
这是第一个测试,testA();
@Test
public void testA() throws InterruptedException {
// We create the first transaction
TransactionStatus statusOne = requireTransaction();
// Create person one
Person pOne = new Person();
pOne.name = "PersonOne";
session().persist(pOne);
// ---> 111) NOTE! We do not commit! Intentionally!
// We requireTransaction again. We should be getting the same transaction status.
TransactionStatus statusTwo = requireTransaction();
if ( !statusTwo.isNewTransaction() ) {
System.out.println("isNewTransaction: false! As expected! Meaning we are getting the original transaction status!");
}
// Create person two
Person pTwo = new Person();
pTwo.name = "PersonTwo";
session().persist(pTwo);
// We will now be committing statusTwo which should actually be the first one, statusOne,
// since we are using propagation required and the previous transaction was never committed
// or rolledback or completed in any other fashion!
getTransactionManager().commit(statusTwo);
// !!!!!!! However !!!!!! Nothing is actually written to the database here!
// This must be a bug. It existed on Spring 4.0.4 and I have upgraded to 4.2.0 and still the same thing happens!
// Lets go on to the next test. testB() below.
// If we now, at 111) instead do, let me repeat the entire logic:
}
这是第二个测试,testA();
@Test
public void testB() throws InterruptedException {
// We create the first transaction
TransactionStatus statusOne = requireTransaction();
Person pOne = new Person();
pOne.name = "PersonOne";
session().persist(pOne);
// -----> 111) NOW WE ARE COMMITTING INSTEAD, SINCE WE ARE ALMOST FORCED TO BUT DO NOT WANT TO
getTransactionManager().commit(statusOne);
// ----> 222) HOWEVER, NOW WE WILL NOT BE ABLE TO ROLLBACK THIS AT A LATER POINT
// We requireTransaction again. We should be getting A NEW transaction status.
TransactionStatus statusTwo = requireTransaction();
if ( statusTwo.isNewTransaction() ) {
System.out.println("isNewTransaction: true! As expected! Meaning we are getting a new transaction status!");
}
Person pTwo = new Person();
pTwo.name = "PersonTwo";
session().persist(pTwo);
getTransactionManager().commit(statusTwo);
// Now we will have two instances in the database, as expected.
// If we instead of committing statusTwo would have done:
// getTransactionManager().rollback(statusTwo)
// then only the last one will be rolledback which is not desired!
// Why are we forced to commit the first one to have any effect on future transactions!
// Delegation will not work like this!
}
清楚了吗?
这显然是一个错误,不是吗?
为什么目的需要与PROPAGATION_REQUIRED的 retransaction 除了通过同一线程销毁未来的提交之外?
为什么 testA() 中对 statusTwo 的提交不足以提交第一个的工作?
这应该以其他方式完成吗?我想不是,对吧?错误!
编辑对于那些建议我使用执行方法的人,很好:
public PlatformTransactionManager getTransactionManager() {
return /** implement this **/ ;
}
@Test
public void testAA() throws InterruptedException {
insertPerson1();
insertPerson2();
}
public void requireTransaction(TransactionCallback<Object> action) {
TransactionTemplate template = new TransactionTemplate(getTransactionManager());
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
template.execute(action);
}
public void insertPerson1() {
requireTransaction(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
Person pOne = new Person();
pOne.name = "PersonOne";
session().persist(pOne);
return null;
}
});
}
public void insertPerson2() {
requireTransaction(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
Person pTwo = new Person();
pTwo.name = "PersonTwo";
session().persist(pTwo);
if ( true ) {
status.setRollbackOnly();
// throw new RuntimeException("aaaaaaa");
}
return null;
}
});
}
在 insertPerson2 上,即使我设置为回滚或抛出异常,仍然插入第一个人!
这意味着,不是一个共享的交易,而是两个独立的交易。
答:
你基本上没有按照预期使用事务 API。参考文档非常精确地介绍了如何使用编程事务:
PlatformTransactionManager manager = … // obtain a transaction manager (DI)
TransactionTemplate template = new TransactionTemplate(manager);
template.execute(status -> {
// Code to be execute in a transaction goes here.
});
这里要注意的一点是,如果你想让代码参与单个事务,那么该代码需要进入回调。这基本上意味着你实际放置这个模板代码的位置取决于你希望你的交易边界有多大。如果您想在事务中运行整个请求,请将此代码放在 a 中并调用 .这基本上会导致所有代码都参与到同一个事务中。ServletFilter
FilterChain.doFilter(…)
只有在回调中执行代码才能确保异常正确触发回滚,而您建议的 API 用法完全忽略了这一点。
可以使用一个对象进行实例化,该对象基本上定义了要创建的事务的特征。TransactionTemplate
TransactionDefinition
一些一般性建议
也就是说,在不进一步了解您引入的所有帮助程序方法的情况下(您使用什么实现?该怎么办?为什么不直接注入到测试中呢?很难做任何进一步的诊断。PlatformTransactionManager
getRepository().getTransactionManager()
PlatformTransactionManager
如果乍一看似乎有问题的行为,请确保将代码简化为基本内容,并确保遵循参考文档的建议。在90%的情况下,感知到的“错误”只是使用事物时的错误,通常隐藏在间接的层中。
如果您仍然认为自己发现了错误,请先在一个规范的地方询问。如果你在相对较短的时间内没有得到答案,想想你是否能够改进这个问题(将代码简化到它的基本要素等)。问得不好(冗长、冗长)的问题通常不会产生回答的动力。
不要将交叉帖子复制并粘贴到多个位置。这通常只会让你真正试图从中寻求帮助的人感到沮丧,因为他们必须寻找你发布的所有地方——时间,他们本可以用来真正帮助你。
StackOverflow 通常是一个很好的开始。如果(错误)行为确实可以确认为错误,仍然可以创建票证。或者,首先创建一个工单(然后只创建一个工单),但要做好准备准备可执行测试用例的准备,要准确,并且最重要的是,已经阅读了参考文档:)。
评论
是的,这是 Spring 事务管理中的一个错误。
目前,我已经使用 ThreadLocal 编写了自己的逻辑来规避此问题。不过,必须寻找一些东西才能正常工作,但必须避免调用 getTransaction(template),因为每次都会返回一个新版本。我不知道为什么。
相反,我在 ThreadLocal 中跟踪 TransactionStatus,并在下次检查它是 REQUIRE 还是 SUPPORTS,并且以前的状态是可以接受的,而不是 comitted 和其他一些边缘情况。
评论
上一个:在聚合物元素之间共享数据更改
下一个:如何从网站上阅读?C# [重复]
评论
PlatformTransactionManager
TransactionTemplate#execute