Spring @Transactional 继承规则

26

我有一组继承自抽象类的@Service bean。我用 @Service@Transactional 标记了每个具体子类服务。抽象超类包含这些服务的公共入口方法。换句话说,我的情况类似于以下内容:

abstract class AbstractService {

    public void process() {
        // Do common initialisation code here
        processSpecific();
        // Do common completion code here
    }

    abstract protected void processSpecific();
}


@Service @Transactional
public class FirstSpecificService extends AbstractService {
    protected void processSpecific() {
        // Do specific processing code here
    }
}


@Service @Transactional
public class SecondSpecificService extends AbstractService {
    protected void processSpecific() {
        // Do different specific processing code here
    }
}
每个具体子类服务中的特定代码都会多次调用DAO层以更改数据库,事务传播类型为REQUIRED
现在,使用上述定义的服务,我发现这些具体子类服务的任何代码中都没有当前事务,并且对DAO层的每个调用都会创建一个新的事务,进行更改,提交事务并返回。
然而,如果我在抽象超类上注释@Transactional,则事务将正确地创建,并且对DAO层的子调用都参与当前事务。
因此,我的问题是,继承@Transactional行为的规则是什么?为什么Spring不在实际实例化的具体子类服务上使用@Transactional?在这种情况下,@Transactional是否需要在超类上,因为那里是公共入口点方法?

1
顺便说一下,我已经查看了相关的SpringSource文档,但似乎没有涉及到这个问题。 - DuncanKinnear
我有完全相同的问题-在我的情况下,即使我没有委派给像processSpecific()这样调用DAO的东西,标记子类@ Transactional也不会使进程似乎有Txn打开。 - redzedi
3个回答

15

从 Spring 事务文档中得知:

注意:在代理模式下(默认情况下),仅截获通过代理传入的“外部”方法调用。这意味着,“自我调用”,即目标对象中的某个方法调用目标对象的其他方法,即使调用的方法标记为 @Transactional,也不会在运行时导致实际的事务!

即使您在具体实现上使用了 @Transactional,并且正在通过您的注释调用处理方法(实际上是事务性的),但是因为此内部调用,子类上的 processSpecific 方法并不是事务性的。

请查看织入。


2
但是代理不会成为“FirstSpecificService”的实例吗?如果是这样,系统将调用该实例的外部“process”方法,并且该实例本身标记为@Transactional。我完全理解标记为@Transactional的内部私有或受保护方法不会影响事务,但这不是我所拥有的。我的整个bean都被标记为@Transactional - DuncanKinnear
1
如果从内部方法调用它,它将不是事务性的。首先,当您从外部调用process方法时,代理实例受事务控制,当process方法调用processSpecific时,Spring不知道事务处理,因为切入点是在代理对象上而不是子类processSpecific方法上进行的。我们曾经遇到过同样的问题,我们添加了加载时间织入,一切都正常工作了。 - Kathir
1
你能解释一下相对于我上面的例子,'load time weaving'是什么吗?它会如何改变这些(人为构造的)示例服务的代码? - DuncanKinnear
你需要在上下文文件中添加context:load-time-weaver/,并在启动应用程序时提供-javaagent参数,例如-javaagent:<spring-agent.jar位置>。我认为在新版本的Spring中,spring-agent是org.springframework.instrument-3.1.1.RELEASE.jar。因此,它应该是-javaagent:<org.springframework.instrument-3.1.1.RELEASE.jar位置>。如果你正在开发Web应用程序并且使用Tomcat,则需要执行不同的步骤才能使其正常工作。如果有疑问,请告诉我。 - Kathir
另一个想法:将这些进程特定的内容作为单独的类进行编写,实现它们自己的接口。 - eis

2

这是一个老问题。但我遇到了类似的情况,并在当前的@Transactional注解javadoc中找到了解释:

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html

在类级别上,这个注解默认适用于声明类和它的子类的所有方法。请注意,它不适用于类层次结构中的祖先类;方法需要在本地重新声明才能参与子类级别的注解。
因此,在使用继承和类级别注解时,如果超类包含任何需要事务处理或调用由子类实现的方法的公共方法,则应该对超类进行注释,而不是对子类进行注释。
在超类的方法中调用子类实现并带有 @Transactional 注解的方法,而没有对超类或该方法进行 @Transactional 注解,这是一个错误。如果两者都有注解,但注解的属性不一致,那也是一个错误。
在良好的设计中,超类中由子类实现的虚拟方法只应由超类使用,并且它们应该因此始终具有受保护的作用域,在子类中不需要任何 @Transactional 注解。

1

1
是的,如上所述,我已经阅读了文档中的所有部分,但似乎没有任何部分适用于这种情况。我的抽象超类不是一个接口,而是由实际的具体子类继承的代码。也许您可以引用一下文档中适用于我的示例的部分。 - DuncanKinnear
2
如果您想确保您的DAO始终参与现有事务(由您的服务发起),则应将DAO配置为@Transactional(propagation = Propagation.MANDATORY),因为使用REQUIRED会在不存在任何事务时创建一个新事务。 - matsev
1
是的,这将是我们在未来捕捉这些问题的一种方式,但仍然无法解释继承规则是什么。 - DuncanKinnear

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