【问题标题】:Spring + Hibernate manually creating transactions, PROPAGATION_REQUIRED fails. BUG?Spring + Hibernate 手动创建事务,PROPAGATION_REQUIRED 失败。漏洞?
【发布时间】:2015-08-04 19:29:18
【问题描述】:

请不要建议我为此使用交易注释。

我遇到了一个似乎与 Spring 处理事务有关的错误。

请看这两个测试用例,cmets在代码中:

以实体类为例:

@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 提交不足以同时提交第一个的工作?

应该以其他方式完成吗?我觉得不会吧?错误!

编辑 对于那些建议我使用执行方法的人,很好:

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 上,即使我设置回滚或抛出异常,第一个人仍然被插入!

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

【问题讨论】:

  • 您的帖子显示了很多代码,但恐怕不是相关部分。你在用什么PlatformTransactionManager?不要自己调用低级 API(您显然不应该这样做),而是重写您的测试以使用 TransactionTemplate#execute 方法。如果问题仍然存在,请在某处发布示例项目,我会看看。 Spring Framework 中的事务管理已经相当成熟,并且在 4.x 行中没有太大变化。该代码是否适用于以前版本的 Spring?

标签: spring hibernate transactions spring-transactions


【解决方案1】:

您基本上不会按预期使用事务 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% 的情况下,感知到的“错误”只是使用事物时的错误,通常隐藏在间接层中。

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

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

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

【讨论】:

  • org.springframework.transaction.PlatformTransactionManager 是我使用的,我使用了一个测试数据库,我相信你已经有一些数据库设置,只需添加 Person 实体并自己测试它。我没有使用错误的 API。它只是坏了。在这里分享我的交易配置非常困难。
  • 关于简短的问题,我尝试将其归结为要领,并尝试将 cmets 包含在代码中。如果您试图避免示例中的冗余,有时可能会更难阅读。
  • 我想在不提交的情况下执行代码,这样我就可以在所有地方重用同一个事务,并在开发人员调用 save() 时通过获取事务并提交来提交。
  • “你完全错了。” - 好吧,我的代码正在运行……你的显然是:没那么多:)。因此,您要么尝试应用建议,要么不尝试。在后一种情况下,我不太明白你为什么要问。不管怎样,被人骂是件好事。干杯。
  • 好吧,对不起。但是我不断收到与使用 execute 方法相同的“建议”。每次调用execute方法都会执行一次提交,随后的调用将创建一个新事务。因此,一个事务永远不会被重用,并且 PROPAGATION_REQUIRED 实际上表现为 PROPAGATION_REQUIRES_NEW。如果事务在没有提交的情况下保持打开状态,我将不得不不使用 execute 方法,老实说,这并没有真正做太多。它的作用如下:pastebin.com/nVT4t7jv 我还添加了一个编辑,以在我的问题中包含一个示例,显示它与执行。
【解决方案2】:

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

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

相反,我在 ThreadLocal 中跟踪 TransactionStatus,并在下次检查它是 REQUIRE 或 SUPPORTS 并且之前的状态是可以接受的,未提交的以及其他一些边缘情况。

【讨论】:

  • 只是将其放在这里以供参考,以供任何可能发现这一点的人希望不会被这种虚假声明误导:它实际上完全按预期工作并记录在案。看我的回答。尽管有你的咆哮,我实际上编辑了我的答案来讨论“我需要一次交易中的一切”(你将问题更改为)并实际处理这个问题。
  • 奥利弗,认真的。您的建议是,使用过滤器不是一个好的解决方案,尤其是当我不使用它来处理请求时。也不是将整个代码放在一个块中。 PROPAGATION_REQUIRED 的全部目的是做您未能解决的问题,即记录的内容:“支持当前事务,如果不存在则创建一个新事务。” .您的建议只是对损坏的东西进行修补。请向我解释一下,第二次 requireTransaction 调用的结果应该是什么?真的应该是新交易吗?
  • 另外,如果之前的某个事务提交失败,为什么新事务提交失败?为什么下一个,应该是一个孤立的代码块,依赖于其他人在同一线程上下文中所做的事情,并阻止它提交?此处需要会导致不稳定问题,您将无法获得任何有效交易。很常见的是,这里的人们会联合起来解雇一些东西并提出补丁工作解决方案。严肃地说,仅针对请求,您有一个解决方案,然后针对其他解决方案?
  • 您要我将所有代码放在一个块中吗? ensureTransaction() 发生了什么; /** 做一些工作 **/ commitTransaction();问题是大多数人永远不会遇到这个问题,因为他们会到处调用 requireTransaction,而且大多数时候他们不像我那样吹毛求疵。但我仍然是对的。
  • 我认为我们可以结束这一切。你坚持正确,世界上没有什么可以改变这一点。甚至没有另外 5 400 个字符的 cmets。干杯。
猜你喜欢
  • 2019-03-05
  • 1970-01-01
  • 2012-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-19
  • 2011-09-24
相关资源
最近更新 更多