Spring @Transactional属性在私有方法上工作吗?

如果我在Spring bean的私有方法上有@ transactional注释,该注释有任何效果吗?

如果@Transactional注释位于公共方法上,则它将工作并打开事务。

public class Bean {
public void doStuff() {
doPrivateStuff();
}
@Transactional
private void doPrivateStuff() {


}
}


...


Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
158358 次浏览

你的问题的答案是否定的——@Transactional如果用于注释私有方法将没有任何影响。代理生成器将忽略它们。

这在Spring手册10.5.6章中有记录:

方法可见性和@Transactional

当使用代理时,您应该应用 @Transactional注释 到具有公共可见性的方法。如果 注释protected, private或 类的包可见方法 @Transactional注释,没有错误 ,但是带注释的方法 没有显示配置 事务设置。考虑到 如果需要,可以使用AspectJ(见下面) 注释非公共方法

答案是否定的。请参见Spring参考:使用@Transactional : < / p >

@Transactional注释可以放在接口定义、接口上的方法、类定义或类上的公共方法之前

默认情况下,@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转换为任何类型方法的运行时行为。

Spring文档解释了这一点

在代理模式下(默认),只有外部方法调用 通过代理进入的都被拦截。这意味着 自调用,实际上是目标对象调用中的一个方法 另一个方法的目标对象,不会导致一个实际 即使调用的方法被标记为 @ transactional . < / p > 考虑使用AspectJ模式(参见下表中的mode属性) 如果您希望用事务包装自调用 好。在这种情况下,首先就不会有代理; 相反,目标类将被编织(也就是说,它的字节代码将被编织 将@Transactional转换为运行时行为

另一种方法是用户BeanSelfAware

问题不是私有的或公共的,问题是:如何调用它以及您使用哪个AOP实现!

如果你使用(默认)Spring Proxy AOP,那么Spring提供的所有AOP功能(比如@Transactional)只有在调用通过代理时才会被考虑。——如果从另一个 bean调用带注释的方法,通常是这样。

这有两个含义:

  • 因为私有方法不能从另一个bean调用(例外是反射),所以它们的@Transactional Annotation不会被考虑在内。
  • 如果方法是公共的,但它是从同一个bean调用的,那么它也不会被考虑在内(这句话只在(默认)使用Spring Proxy AOP的情况下是正确的)。

@See Spring参考:第9.6章代理机制

恕我直言,您应该使用aspectJ模式,而不是Spring代理,这将克服这个问题。AspectJ事务方面甚至被编织到私有方法中(Spring 3.0检查)。

是的,可以在私有方法上使用@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构建进行测试,作为完整性检查。

如果你需要在事务中包装一个私有方法,而不想使用AspectJ,你可以使用TransactionTemplate

@Service
public class MyService {
@Autowired
private TransactionTemplate transactionTemplate;


private void process() {
transactionTemplate.executeWithoutResult(status -> processInTransaction());
}


private void processInTransaction(){
//...
}
}

@loonis建议使用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是否重用现有事务,但这段代码肯定会这样做。