Spring + Hibernate 手动创建事务,PROPAGATION_REQUIRED失败。错误?

Spring + Hibernate manually creating transactions, PROPAGATION_REQUIRED fails. BUG?

提问人:mjs 提问时间:8/5/2015 最后编辑:Naren Muralimjs 更新时间:1/9/2018 访问量:2065

问:

请不要建议我为此使用事务注释。

我遇到了一个似乎与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 上,即使我设置为回滚或抛出异常,仍然插入第一个人!

这意味着,不是一个共享的交易,而是两个独立的交易。

hibernate 事务 spring-transactions

评论

0赞 Stephane Nicoll 8/5/2015
你的帖子显示了很多代码,但恐怕没有相关的部分。你在用什么?与其自己调用低级 API(这显然不应该这样做),不如重写测试以改用该方法。如果问题仍然存在,请在某处发布一个示例项目,我会看看。Spring Framework 中的事务管理已经相当成熟,在 4.x 系列中没有太大变化。该代码是否适用于以前版本的 Spring?PlatformTransactionManagerTransactionTemplate#execute

答:

8赞 Oliver Drotbohm 8/5/2015 #1

你基本上没有按照预期使用事务 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 中并调用 .这基本上会导致所有代码都参与到同一个事务中。ServletFilterFilterChain.doFilter(…)

只有在回调中执行代码才能确保异常正确触发回滚,而您建议的 API 用法完全忽略了这一点。

可以使用一个对象进行实例化,该对象基本上定义了要创建的事务的特征。TransactionTemplateTransactionDefinition

一些一般性建议

也就是说,在不进一步了解您引入的所有帮助程序方法的情况下(您使用什么实现?该怎么办?为什么不直接注入到测试中呢?很难做任何进一步的诊断。PlatformTransactionManagergetRepository().getTransactionManager()PlatformTransactionManager

如果乍一看似乎有问题的行为,请确保将代码简化为基本内容,并确保遵循参考文档的建议。在90%的情况下,感知到的“错误”只是使用事物时的错误,通常隐藏在间接的层中。

如果您仍然认为自己发现了错误,请先在一个规范的地方询问。如果你在相对较短的时间内没有得到答案,想想你是否能够改进这个问题(将代码简化到它的基本要素等)。问得不好(冗长、冗长)的问题通常不会产生回答的动力。

不要将交叉帖子复制并粘贴到多个位置。这通常只会让你真正试图从中寻求帮助的人感到沮丧,因为他们必须寻找你发布的所有地方——时间,他们本可以用来真正帮助你。

StackOverflow 通常是一个很好的开始。如果(错误)行为确实可以确认为错误,仍然可以创建票证。或者,首先创建一个工单(然后只创建一个工单),但要做好准备准备可执行测试用例的准备,要准确,并且最重要的是,已经阅读了参考文档:)。

评论

0赞 mjs 8/5/2015
org.springframework.transaction.PlatformTransactionManager是我使用的,我使用testdatabase,我相信你有一些数据库设置alrady,只需添加Person实体并自己测试即可。我没有错误地使用API。它只是坏了。很难在这里分享我的交易配置。
0赞 mjs 8/5/2015
关于简短的问题,我试图将其归结为基本要素,并尝试将注释包含在代码中。有时,如果您尝试避免示例中的冗余,则阅读起来可能会困难得多。
0赞 mjs 8/5/2015
我想在不提交的情况下执行代码,这样我就可以在任何地方重用相同的事务,并在开发人员调用 save() 时通过获取事务并提交来提交。
0赞 Oliver Drotbohm 8/5/2015
“你完全错了。” - 好吧,我的代码正在工作......显然,乌尔:没那么:)。所以,你要么尝试应用建议,要么不应用。在后一种情况下,我不太明白你为什么首先要问。无论如何,被吠叫真是太好了。干杯。
0赞 mjs 8/5/2015
好吧,对不起。但是我不断得到与我使用执行方法相同的“建议”。execute 方法每次被调用时都会执行一次提交,后续调用将创建一个新的事务。因此,事务永远不会被重用,PROPAGATION_REQUIRED实际上表现得很PROPAGATION_REQUIRES_NEW。如果事务在没有提交的情况下保持打开状态,我将不得不不使用 execute 方法,老实说,它并没有真正做太多事情。这是它的作用: pastebin.com/nVT4t7jv 我还添加了一个编辑,以在我的问题中包含一个示例,以显示它与执行。
-6赞 mjs 8/5/2015 #2

是的,这是 Spring 事务管理中的一个错误。

目前,我已经使用 ThreadLocal 编写了自己的逻辑来规避此问题。不过,必须寻找一些东西才能正常工作,但必须避免调用 getTransaction(template),因为每次都会返回一个新版本。我不知道为什么。

相反,我在 ThreadLocal 中跟踪 TransactionStatus,并在下次检查它是 REQUIRE 还是 SUPPORTS,并且以前的状态是可以接受的,而不是 comitted 和其他一些边缘情况。

评论

2赞 Oliver Drotbohm 8/5/2015
只是把它放在这里供参考,对于任何可能发现这一点的人,希望不会被这个错误的说法误导:它实际上完全按照预期和记录工作。看看我的答案。尽管你咆哮,我实际上编辑了我的答案,以讨论“我需要在一笔交易中完成所有事情”(你把问题改成了),并实际解决这个问题。
0赞 mjs 8/5/2015
奥利弗,说真的。你的建议,使用过滤器不是一个好的解决方案,尤其是当我不将其用于请求时。也不是将整个代码放在一个块中。PROPAGATION_REQUIRED的全部目的是做你没有解决的问题,即记录在案的内容:“支持当前交易,如果不存在,则创建一个新交易。你的建议只是对损坏的东西的补丁。请向我解释一下,第二次requireTransaction调用的后果应该是什么?这真的应该是一笔新交易吗?
0赞 mjs 8/5/2015
另外,如果以前的某个事务未能提交,为什么新事务无法提交?为什么下一个,应该是一个独立的代码块,应该依赖于其他人在同一线程上下文中所做的操作,并阻止它提交?此处的 REQUIRED 会导致不稳定问题,您将无法获得任何有效的交易。SO 上的人们联合起来解雇东西并提出修补工作解决方案是很常见的。当然,仅对于请求,您有一个解决方案,然后对于其他解决方案?
0赞 mjs 8/5/2015
你想让我把所有代码放在一个块里吗?ensureTransaction() 发生了什么;/** 做一些工作 **/ commitTransaction();问题是大多数人永远不会遇到这个问题,因为他们会到处调用requireTransaction,而且大多数时候他们不会像我一样吹毛求疵。但我仍然是对的。
1赞 Oliver Drotbohm 8/5/2015
我认为我们可以结束这种情况。你坚持做对,世界上没有什么可以改变这一点。甚至没有另外 5 400 个字符的评论。干杯。