我应该在接口定义还是实现类上放置 @Transactional 注解?

105

代码标题中的问题:

@Transactional (readonly = true)
public interface FooService {
   void doSmth ();
}


public class FooServiceImpl implements FooService {
   ...
}

对比

public interface FooService {
   void doSmth ();
}

@Transactional (readonly = true)
public class FooServiceImpl implements FooService {
   ...
}
5个回答

144

来自 http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Spring团队建议您只在具体类上使用@Transactional注释,而不是在接口上使用。您当然可以在接口(或接口方法)上放置@Transactional注释,但如果您正在使用基于类的代理,则这仅在使用基于接口的代理时才按预期工作。注释不会被继承意味着,如果您使用基于类的代理,则事务设置将无法被基于类的代理基础设施识别,并且对象将不会被包装在事务性代理中(这将是明显错误的)。因此,请遵循Spring团队的建议,只在具体类(及其方法)上使用@Transactional注释。

注意:由于此机制基于代理,因此仅针对通过代理传入的“外部”方法调用进行拦截。这意味着,“自我调用”,即目标对象内的方法调用目标对象的其他方法,即使被标记为@Transactional,也不会在运行时导致实际事务!

(第一句话已经加重强调,其他加重强调来自原文。)


查看Spring源码,我们可以发现JdkDynamicAopProxy在每次方法调用时都会遍历所有的bean advisors(参见DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice()),在声明式事务设置的情况下,这些advisors中包括BeanFactoryTransactionAttributeSourceAdvisor。接着会调用TransactionAttributeSourcePointcut#matches()方法来收集与事务相关的信息。该方法会传入目标类,并且始终可以遍历该类实现的所有接口。现在:为什么这种方式不能可靠地工作呢? - dma_k
2
@dma_k - Java运行时只能直接访问继承自超类的类注释或根本没有继承的方法注释。所以Pointcut.matches()实际上必须递归遍历所有接口,使用反射找到“真正”的重写方法,在处理桥方法、重载、可变参数、泛型、代理等方面,并且当然要做得。然后你还要处理钻石继承——可能有许多继承的@Transactional注释,哪一个应用?所以,虽然这一切都是可能的,但我不责怪Spring。http://jira.springsource.org/browse/SPR-975 - Peter Davis
对于任何想知道的人,这个仍然适用于11年后(在Spring 5.3中):https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/data-access.html#transaction-declarative-annotations - Xr.
如果我同时将它放在类和接口中会发生什么? - Amir
@Amir 在某些情况下,它可能无法正常工作,并且需要花费您数小时/数天才能找出问题所在。 - Romain Hippeau
显示剩余2条评论

12

Spring的建议是对具体实现类进行注释,而不是接口。在接口上使用注释并不是错误的,但可能会误用该功能并无意中绕过@Transaction声明。

如果你在接口中标记了某些事务,并在spring的其他地方引用了它的一个实现类,则不太明显的是,spring创建的对象将不会遵守@Transactional注释。

实际上看起来像这样:

public class MyClass implements MyInterface { 

    private int x;

    public void doSomethingNonTx() {}

    @Transactional
    public void toSomethingTx() {}

}

10
你可以将它们放在接口上,但要注意,在某些情况下可能不会发生交易。请参阅Spring文档中第10.5.6节的第二个提示:

Spring建议您只对具体类(和具体类的方法)使用@Transactional注解,而不是对接口进行注解。当然,您可以在接口(或接口方法)上放置@Transactional注解,但只有在使用基于接口的代理时,它才能按照您的预期工作。由于Java注解不会从接口继承,这意味着如果您使用基于类的代理(proxy-target-class="true")或基于编织的切面(mode="aspectj"),则代理和编织基础设施不会识别事务设置,并且对象将不会被包装在事务代理中,这将是非常糟糕的。

基于这个原因,我建议将它们放在实现类上。
此外,对我来说,事务似乎是实现细节,因此它们应该在实现类中。想象一下,如果有用于日志记录或测试实现(模拟)的包装器实现,它们不需要是事务性的。

1

支持在具体类上使用 @Transactional:

我通常喜欢将解决方案架构为三个部分:API、实现和Web(如果需要)。我尽最大努力保持API的轻量/简单/POJO,通过最小化依赖项来实现。如果您在分布式/集成环境中运行它并且必须经常共享API,则这尤其重要。

在API部分添加 @Transactional 需要Spring库,但我认为这不是有效的做法。因此,我更喜欢在实现中添加它,即事务正在运行的位置。


0
只要你的IFC的所有可预见实现者都关心TX数据(交易不仅仅是数据库处理的问题),将其放在接口上就可以了。如果该方法不关心TX(但你需要将其放在那里以供Hibernate或其他用途),则将其放在impl上。
此外,最好将@Transactional放在接口中的方法上:
public interface FooService {
    @Transactional(readOnly = true)
    void doSmth();
}

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