Spring @Transactional 属性是不是适用于私有方法?
Posted
技术标签:
【中文标题】Spring @Transactional 属性是不是适用于私有方法?【英文标题】:Does Spring @Transactional attribute work on a private method?Spring @Transactional 属性是否适用于私有方法? 【发布时间】:2011-05-22 17:30:56 【问题描述】:如果我在 Spring bean 中的私有方法上有一个 @Transactional -annotation,这个注解有什么作用吗?
如果@Transactional
注释在公共方法上,它会起作用并打开一个事务。
public class Bean
public void doStuff()
doPrivateStuff();
@Transactional
private void doPrivateStuff()
...
Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
【问题讨论】:
【参考方案1】:您的问题的答案是否定的 - @Transactional
如果用于注释私有方法将无效。代理生成器将忽略它们。
这记录在Spring Manual chapter 10.5.6:
方法可见性和
@Transactional
使用代理时,您应该申请 仅限
@Transactional
注释 到具有公共可见性的方法。如果 您确实注释了受保护的、私有的或 包可见的方法@Transactional
注解,没有错误 被提出,但带注释的方法 不显示配置的 交易设置。考虑 如果需要,使用 AspectJ(见下文) 注释非公共方法。
【讨论】:
你确定吗?我不指望它会有所作为。 代理风格如果是Cglib呢? 我使用下面的正则表达式@Transactional([^](?!public))+ \
来查找我们代码库中可能没有任何影响的注释(因为它们在私有、受保护、包私有方法上)。没有找到“没有代理的自我引用”——当然是对公共方法的调用——是否有插件或其他东西可以发现这些?【参考方案2】:
答案是否定的。请看Spring Reference: Using @Transactional :
@Transactional
注解可以放在接口定义、接口上的方法、类定义或类上的 public 方法之前
【讨论】:
【参考方案3】:默认情况下,@Transactional
属性仅在对从 applicationContext 获得的引用调用带注释的方法时起作用。
public class Bean
public void doStuff()
doTransactionStuff();
@Transactional
public void doTransactionStuff()
这将打开一个事务:
Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();
这不会:
Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
Spring Reference: Using @Transactional
注意:在代理模式下(默认),只有通过代理传入的“外部”方法调用会被拦截。这意味着“自调用”,即目标对象中的一个方法调用目标对象的某个其他方法,即使被调用的方法标有
,也不会在运行时导致实际事务>@Transactional
!如果您希望自调用也包含在事务中,请考虑使用 AspectJ 模式(见下文)。在这种情况下,首先不会有代理;相反,目标类将被“编织”(即其字节码将被修改),以便将
@Transactional
转变为任何类型方法的运行时行为。
【讨论】:
你的意思是 bean = new Bean();? 不。如果我使用 new Bean() 创建 bean,则至少在不使用 Aspect-J 的情况下,注释将永远无法工作。 谢谢!这解释了我观察到的奇怪行为。这个内部方法调用限制非常反直觉......【参考方案4】:Spring Docs 解释说
在代理模式下(默认),只有外部方法调用 通过代理进入的被拦截。这意味着 自调用,实际上是目标对象调用的方法 目标对象的另一种方法,不会导致实际 即使调用的方法标记为 @Transactional。
考虑使用 AspectJ 模式(见下表中的模式属性) 如果您希望自调用与事务一起包装为 好吧。在这种情况下,首先不会有代理; 相反,目标类将被编织(也就是说,它的字节码将 被修改)以便将@Transactional 转换为运行时行为 任何一种方法。
另一种方式是用户BeanSelfAware
【讨论】:
您可以添加对BeanSelfAware
的引用吗?它看起来不像 Spring 的类
@asgs 假设,它是关于自我注入(提供一个 bean,其自身的实例包装到代理中)。您可以在***.com/q/3423972/355438 中查看示例。【参考方案5】:
问题不是私有的或公开的,问题是:它是如何被调用的,你使用的是哪个 AOP 实现!
如果您使用(默认)Spring Proxy AOP,那么只有在调用通过代理时才会考虑 Spring 提供的所有 AOP 功能(如 @Transactional
)。 -- 如果从 另一个 bean 调用带注释的方法,通常会出现这种情况。
这有两个含义:
因为不能从另一个 bean 调用私有方法(反射除外),所以它们的@Transactional
注解不被考虑在内。
如果该方法是公共的,但它是从同一个 bean 调用的,也不会被考虑在内(此语句仅在使用(默认)Spring Proxy AOP 时才正确)。
@见Spring Reference: Chapter 9.6 9.6 Proxying mechanisms
恕我直言,您应该使用 aspectJ 模式,而不是 Spring 代理,这样可以解决问题。并且 AspectJ 事务方面甚至被编织到私有方法中(检查了 Spring 3.0)。
【讨论】:
这两点不一定都是真的。第一个是不正确的——私有方法可以被反射调用,但是代理发现逻辑选择不这样做。第二点仅适用于基于接口的 JDK 代理,而不适用于基于 CGLIB 子类的代理。 @skaffman: 1 - 我让我的陈述更准确,2. 但是默认代理是基于接口的 - 不是吗? 这取决于目标是否使用接口。如果没有,则使用 CGLIB。 可以告诉我为什么 cglib 不能但 aspectj 可以的原因或一些参考? 来自答案块中链接的引用,如果您想使用 Spring Proxies [默认环境],请在 doStuff() 上添加注释并使用 ((Bean) AopContext.currentProxy()) 调用 doPrivateStuff() .doPrivateStuff();如果传播被重复[默认环境],它将在同一个事务中执行这两种方法。【参考方案6】:是的,可以在私有方法上使用@Transactional,但正如其他人提到的那样,这不会开箱即用。您需要使用 AspectJ。我花了一些时间来弄清楚如何让它工作。我会分享我的结果。
我选择使用编译时编织而不是加载时编织,因为我认为这是一个整体更好的选择。另外,我使用的是 Java 8,所以您可能需要调整一些参数。
首先,添加aspectjrt的依赖。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
然后添加 AspectJ 插件以在 Maven 中进行实际的字节码编织(这可能不是一个最小的示例)。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
最后将这个添加到你的配置类中
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
现在您应该可以在私有方法上使用@Transactional。
对这种方法的一个警告:您需要配置您的 IDE 以了解 AspectJ,否则如果您通过 Eclipse 运行应用程序,例如它可能无法工作。确保您针对直接 Maven 构建进行测试作为健全性检查。
【讨论】:
如果代理方法是cglib,不需要实现方法应该是public的接口,那么可以在private方法上使用@Transactional吗? 是的,它适用于私有方法,并且没有接口!只要AspectJ配置得当,基本保证了工作方法装饰器。 user536161 在他的回答中指出,它甚至可以用于自调用。真的很酷,只是有点吓人。【参考方案7】:如果您需要在事务中包装私有方法并且不想使用 AspectJ,可以使用TransactionTemplate
。
@Service
public class MyService
@Autowired
private TransactionTemplate transactionTemplate;
private void process()
transactionTemplate.executeWithoutResult(status -> processInTransaction());
private void processInTransaction()
//...
【讨论】:
很好地展示TransactionTemplate
的用法,但请调用第二种方法..RequiresTransaction
而不是..InTransaction
。总是说出你想在一年后阅读它的方式。此外,我认为它是否真的需要第二个私有方法:要么将其内容直接放在匿名 execute
实现中,要么如果它变得混乱,则可能表明将实现拆分为另一个服务,然后您可以注释@Transactional
.
@Stuck,第二种方法确实没有必要,但它回答了原始问题,即如何在私有方法上应用弹簧事务
是的,我已经对答案投了赞成票,但想分享一些关于如何应用它的背景和想法,因为我认为从架构的角度来看,这种情况可能表明存在设计缺陷。【参考方案8】:
与@loonis suggested 使用TransactionTemplate 的方式相同,可以使用此帮助程序组件(Kotlin):
@Component
class TransactionalUtils
/**
* Execute any [block] of code (even private methods)
* as if it was effectively [Transactional]
*/
@Transactional
fun <R> executeAsTransactional(block: () -> R): R
return block()
用法:
@Service
class SomeService(private val transactionalUtils: TransactionalUtils)
fun foo()
transactionalUtils.executeAsTransactional transactionalFoo()
private fun transactionalFoo()
println("This method is executed within transaction")
不知道TransactionTemplate
是否重用现有事务,但这段代码肯定可以。
【讨论】:
以上是关于Spring @Transactional 属性是不是适用于私有方法?的主要内容,如果未能解决你的问题,请参考以下文章
Spring @Transactional 属性是不是适用于私有方法?
事务隔离属性spring传播属性 @Transactional注解
coding++:Spring中的@Transactional(rollbackFor = Exception.class)属性详解
@Transactional的参数意义及使用。spring中事务注解的配置情况