我应该将@Transactional 注释放在哪里:在接口定义或实现类中?

Posted

技术标签:

【中文标题】我应该将@Transactional 注释放在哪里:在接口定义或实现类中?【英文标题】:Where should I put @Transactional annotation: at an interface definition or at an implementing class? 【发布时间】:2011-03-08 09:38:57 【问题描述】:

代码中标题的问题:

@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 
   ...

【问题讨论】:

【参考方案1】:

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

Spring 团队的建议是您只使用 @Transactional 注释来注释具体类,而不是注释接口。您当然可以将 @Transactional 注释放在接口(或接口方法),但这仅在您使用基于接口的代理时才能正常工作。注释不被继承这一事实意味着,如果您使用基于类的代理,则基于类的代理基础架构将无法识别事务设置,并且对象不会被包装在事务代理中(这绝对是糟糕)。所以请务必听取 Spring 团队的建议,仅使用 @Transactional 注释来注释具体类(以及具体类的方法)。

注意:由于这种机制是基于代理的,只有通过代理传入的“外部”方法调用才会被拦截。这意味着“自调用”,即目标内的方法对象调用目标对象的一些其他方法,即使调用的方法标有@Transactional!

,在运行时也不会导致实际事务

(在第一句中添加了重点,原文中的其他重点。)

【讨论】:

Big +1 顺便说一句,我冒昧地把这句话变成了引用,所以人们不会感到困惑,并从原文中添加了斜体等。 我之前在 Spring 文档中读过此声明,但我仍然无法理解为什么 基于类的代理基础架构无法识别事务设置?我个人认为,这只是 Spring 实现的限制,而不是底层代理基础设施的问题。 查看 Spring 源代码可以发现,JdkDynamicAopProxy 在每个方法调用上都会遍历所有 bean 顾问(另请参阅 DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice()),在声明性事务设置的情况下,包括 BeanFactoryTransactionAttributeSourceAdvisor。依次调用TransactionAttributeSourcePointcut#matches(),它应该收集与事务相关的信息。它被传递给目标类,它总是可以遍历这个类实现的所有接口。现在:为什么这不能可靠地工作? @dma_k - Java 运行时只允许直接访问从超类继承的类注解,或者根本没有继承的方法注解。因此,Pointcut.matches() 必须实际递归遍历所有接口,找到具有反射的“真实”覆盖方法,处理桥接方法,重载,可变参数,泛型,代理,当然快速 .然后你必须处理菱形继承——可能许多继承的@Transactional 注释中的哪一个会适用?所以,尽管这一切都是可能的,但我并不责怪 Spring。 jira.springsource.org/browse/SPR-975 对于任何想知道的人,这仍然适用于 11 年后(春季 5.3):docs.spring.io/spring-framework/docs/5.3.x/reference/html/…【参考方案2】:

Spring 的recommendation 是您注释具体实现而不是接口。在接口上使用注解并没有错,只是可能会滥用该功能并无意中绕过您的@Transaction 声明。

如果你在接口中标记了事务性的东西,然后在 spring 的其他地方引用了它的实现类之一,那么 spring 创建的对象不会尊重 @Transactional 注释并不是很明显。

实际上它看起来像这样:

public class MyClass implements MyInterface  

    private int x;

    public void doSomethingNonTx() 

    @Transactional
    public void toSomethingTx() 


【讨论】:

【参考方案3】:

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

Spring 建议您仅使用 @Transactional 注释来注释具体类(和具体类的方法),而不是注释接口。您当然可以将@Transactional 注释放在接口(或接口方法)上,但这仅在您使用基于接口的代理时才起作用。 Java 注释不是从接口继承的事实意味着,如果您使用基于类的代理 (proxy-target-class="true") 或基于编织的方面 (mode="aspectj"),则事务设置为不被代理和编织基础设施识别,并且对象不会被包装在事务代理中,这绝对是糟糕的。

出于这个原因,我建议将它们放在实现中。

另外,对我来说,事务似乎是一个实现细节,所以它们应该在实现类中。想象一下,拥有不需要事务性的日志记录或测试实现(模拟)的包装器实现。

【讨论】:

【参考方案4】:

在具体类上支持@Transactional:

我通常更喜欢在 3 个部分中构建解决方案:API、实现和 Web(如果需要)。我尽我所能通过最小化依赖来保持 API 尽可能轻/简单/POJO。如果您在必须大量共享 API 的分布式/集成环境中播放它,这一点尤其重要。

将@Transactional 放入API 部分需要Spring 库,恕我直言,这是无效的。所以我更喜欢将它添加到运行事务的实现中。

【讨论】:

【参考方案5】:

只要您的 IFC 的所有可预见实施者都关心 TX 数据(事务不仅仅是数据库处理的问题),将它放在接口上就可以了。如果该方法不关心 TX(但您需要将它放在那里以用于 Hibernate 或其他),请将其放在 impl 上。

另外,将@Transactional 放在接口中的方法上可能会更好一些:

public interface FooService 
    @Transactional(readOnly = true)
    void doSmth();

【讨论】:

以上是关于我应该将@Transactional 注释放在哪里:在接口定义或实现类中?的主要内容,如果未能解决你的问题,请参考以下文章

在哪里放置@Transactional?在接口规范或实现中? [复制]

我必须在哪里使用注释@Service @Transactional

if-then-else 结构中的注释应该放在哪里? [关闭]

@Transactional 注释未按预期工作

如何将 @Transactional 与 Spring Data 一起使用?

整个类的事务注释+不包括单个方法