请不要建议我为此使用交易注释。
我遇到了与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的requireTransaction而不是通过同一个线程销毁未来的提交?
为什么 testA()中 statusTwo 的提交不足以提交第一个上的工作?
这应该以其他方式完成吗?我想不是吧?错误!
修改 对于那些建议我使用execute方法的人,好的:
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上,即使我设置为回滚,或者抛出异常,仍然会插入第一个人!
这意味着,不是一个共享事务,而是两个独立事务。
答案 0 :(得分:7)
您基本上没有按照预期使用交易API。关于如何使用程序化交易,reference documentation非常准确:
PlatformTransactionManager manager = … // obtain a transaction manager (DI)
TransactionTemplate template = new TransactionTemplate(manager);
template.execute(status -> {
// Code to be execute in a transaction goes here.
});
要实现的重点是,如果您希望代码参与单个事务,那么该代码需要进入回调。这基本上意味着您实际放置此模板代码的位置取决于您希望事务边界的大小。如果您想在事务中运行整个请求,请将此代码放在ServletFilter
中并调用FilterChain.doFilter(…)
。这基本上会导致所有代码都参与同一笔交易。
只有在回调中执行代码才能确保异常正确触发回滚,这是您建议的API完全忽略的用法。
TransactionTemplate
可以使用TransactionDefinition
对象进行实例化,该对象基本上定义了要创建的事务的特征。
那就是说,在没有进一步了解你介绍的所有辅助方法的情况下(你使用了什么PlatformTransactionManager
实现?getRepository().getTransactionManager()
做了什么?为什么不直接将PlatformTransactionManager
注入测试?)很难做进一步的诊断。
如果您在快速浏览时遇到似乎已被破坏的行为,请确保将代码缩减为基本要素,并确保遵循参考文档的建议。在90%的情况下,感知到的&#34; bug&#34;在使用东西时只是一个错误,通常埋藏在间接层中。
如果您仍然认为自己发现了错误,请先在单个规范位置询问。如果您在相对较短的时间内没有得到答案,请考虑一下您是否能够改进问题(将代码减少到必需品等)。质疑不好((工资,冗长)的问题通常不会创造回应的动力。
不要抄袭&amp;将交叉帖子粘贴到多个位置。这通常只会导致你实际上试图获得帮助的人感到沮丧,因为他们必须追捕你发布的所有地方 - 时间,他们可能已经习惯了实际帮助你。StackOverflow通常是一个很好的开始。如果(错误)行为确实可以确认为错误,则仍可以创建票证。或者,首先创建一个票证(然后是一个票证),但是更准备好准备一个可执行的测试用例,准确而且 - 最重要的是 - 已经阅读了参考文档:)。
答案 1 :(得分:-6)
是的,这是Spring事务管理中的一个错误。
目前,我已经使用ThreadLocal编写了自己的逻辑来规避这个问题。为此,我们必须寻找一些可以工作的东西,但是必须避免调用getTransaction(模板),因为每次都会返回一个新版本。我不知道为什么。
相反,我会在ThreadLocal中跟踪TransactionStatus,并在下次检查它是否为需求或支持,以前的状态是可以接受的,不是评论和其他一些边缘情况。