AOP,Spring和事务范围

4
想象一下一个使用Spring、JDBC和AOP的事务性多线程Java应用程序,其中有n个类在m个包中参与数据库事务。现在假设需要将任意一组类限定在一个事务范围内。此外,在该范围内始终存在一个类T,当其被调用时会提交事务。
为了更清晰地说明,让我举个例子: 给定包A、B、Z和类A.Foo、B.Bar和Z.T。 分别调用了以下各自类的实例(可能由不同的调用者调用,中间还有其他类):A.Foo、B.Bar、A.Foo和Z.T。 只有在调用Z.T之后,事务才会被提交。如果应用程序因任何原因关闭,则除非涉及到Z.T,否则事务将永远不会被提交。
实例可以相互调用,并且如前所述,没有共同的入口点从单个入口点调用所有实例(例如服务层),这使得Spring的事务标签成为易于处理的目标。
现在问题是:是否可以使用切面解决此问题?如果可以,基本方法是什么? 谢谢。

你能否澄清一下,您是否在Web容器内部,您的交易是否总是作为Web请求的一部分发生,以及每个请求中是否有多个交易? - skaffman
事务不应该知道或关心您是否正在使用Web前端。服务层才知道业务单元,那里声明了事务。 - duffymo
我不在Web容器中 - 应用程序是独立运行的。duffymo是正确的,在这种情况下,Web前端不应该有影响(如果存在,也不会改变我面临的问题)。 - lopass
如果他可以使用请求或会话范围的 Spring Bean 来处理他的事务本地状态,那么它是相关的。但是他不在 Web 容器中,因此他没有这个选项,这将很好地解决问题。 - skaffman
3个回答

2
您不需要一个单一的入口点,但您需要能够将事务拦截器应用于所有入口点,以便可重入调用可以参与相同的事务。假设您可以这样做,您可以使用ThreadLocal标志和自定义org.springframework.transaction.support.TransactionSynchronization实现来完成此操作。
当提交是安全的时候,您可以修改Z.T以设置ThreadLocal标志。在您的TransactionSynchronization.beforeCommit()实现中,该实现从PlatformTransactionManager调用,您可以检查标志并使用它来确定是否允许提交继续进行。如果不存在该标志,则可以通过抛出RuntimeException来强制回滚。
一个注意事项是如果您有其他类型的事务(不涉及您描述的3个协调类),您需要确保它们不会意外地回滚。为此,您可以通过另一个ThreadLocal标志在A.Foo、B.Bar和Z.T中标记此“特殊事务”,然后在上述beforeCommit()方法的保护子句中检查该标志。伪代码:

void beforeCommit() {
  if in special transaction
    if commit flag not set
       throw new RuntimeException("cancel transaction")
    end if
  end if
end

显然,这是一种黑客方式,我不建议在全新的系统中这样做 :).

(提示:此处的“hack”指的是非常规或者不合适的解决方法)

那是一个我可以开始工作的起点。感谢您提供的有用答案。 - lopass

2
Spring的惯用语建议有一个服务接口,了解工作单元和处理关系数据库的持久性接口。服务接口中的方法应该与您的用例紧密映射。服务实现知道所有模型和持久性包和类,以完成用例的目标。
“实例可以互相调用,并且如已经提到的,没有一个共同的入口点从单个入口点(如服务层)调用所有实例,这将成为Spring事务标记的易于目标。”
这句话告诉我,您正在以一种不太适合Spring惯用语的方式进行操作。很难确切地知道您想要什么,但听起来您正在抛弃Spring建议的两个最重要的层。如果反其道而行之似乎很困难,也许是您的设计需要重新考虑。
“...不同的调用者和其他类之间...” - 也许您需要在这些调用者上单独声明事务。
您可以使用方面在XML配置中声明事务,使用Spring AOP或AspectJ。Spring 2.5及更高版本现在提供了使用注释的选项,如果您更喜欢它们而不是XML配置。
您的描述对我来说非常令人困惑。也许这就是您遇到困难的原因之一。我会重新思考或澄清。

1
我正在处理一个老旧的内部框架,因此重构为Spring的惯用语很困难。虽然给出的描述是通用的,但我并没有看到混淆。三个不同类的实例a、b、z分布在三个不同的包中,可以从应用程序的任何地方调用,并一起形成一个单一的事务。只有当这三个类中的第三个类被调用时,事务才会被提交。所以,a被调用,b被调用,c被调用->提交。B被调用,a被调用,没有c被调用,没有提交。A被调用,c被调用,提交。 - lopass
“遗留代码”?Spring并不算太老了。听起来像是那些刚开始使用Spring的人不熟悉。我很难相信使用Spring进行重构会那么困难。某个对象必须创建这些正在交互的实例。对象的所有者也应该拥有事务。“应用程序内的任何位置”——听起来一团糟。抱歉,无法帮助您。 - duffymo
我可以说:这个框架做了它应该做的事情,在这方面它设计得很好。将其改变为Spring所接受的习惯用法是可能的,但工作量太大了。 - lopass

0

使用Spring事务和AOP可以实现,但需要进行一些“hack”操作...

您需要在所有入口点处启动事务 - 只有从启动事务时才能提交,并且您需要在此内部添加第二个方面来控制是否提交。

现在告诉Spring回滚事务的唯一方法是跨事务边界抛出异常。因此,如果进入会导致提交的区域Z,则需要将某些内容放入线程本地变量中(也可能通过方面),以便“内部”方面找到并因此不会抛出异常以回滚事务。如果您没有进入Z,则线程本地变量将无法获取标志,当您穿过内部方面时,异常将被抛出以回滚事务。您可能需要吞下该异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接