Spring AOP应用于多数场景
- 缓存
- 权限
- 懒加载
- 日志
- 事务
- 。。。
这一篇将通过AOP源码的实现层面,结合事务的传播机制,来理解AOP是如何管理事务的。
生成AopProxy代理
Spring在启动期间,会将待注入的类注入到容器中,期间它会判断该类是否需要被代理,是的话将会创建该类实例的代理对象,代码片段如下
方法位于org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
先看一下生成代理对象的时序图
检测是否要代理bean(AbstractAutoProxyCreator#wrapIfNecessary)
- 检查是否有必要包装一下Bean,即是否需要往bean上添加代理对象,具体的检测逻辑如下
protected List findEligibleAdvisors(Class> beanClass, String beanName) {
// 获取要在自动代理中使用的候选顾问(增强器,由Advisor和Advice组成)列表
List candidateAdvisors = findCandidateAdvisors();
// 过滤得到合格的候选顾问列表
List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
- 检测返回的可用的增强器(
Advisor
和Advice
)列表后,如果列表不为null,则将其作为被代理的bean的状态缓存起来,并且开始创建代理对象
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
// 缓存当前bean的代理状态
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 缓存代理类实例
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
创建代理工厂,通过工厂创建bean的代理对象
protected Object createProxy(Class> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 创建代理工厂,并且复制当前实例的相关属性
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
// 是否设置直接代理目标类,而不是目标类接口
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 根据可用的增强器列表构建真正适用于该bean的增强器列表
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
// 设置目标代理类
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
代理工厂选取合适的AOP代理(ProxyFactory#getProxy)
上面创建的工厂代理类,配置了相应的属性后,将选取合适的AOP代理,并且生成代理对象
创建AOP代理
protected final synchronized AopProxy createAopProxy() {
// 如果代理未激活,则激活该代理配置
if (!this.active) {
activate();
}
// 获取aop代理工厂类,并创建aop代理
return getAopProxyFactory().createAopProxy(this);
}
Spring内置的aop代理有两种:JDK和Cglib。之前创建的代理工厂在下面创建方法作为形参
- 使用JDK动态代理
- proxyTargetClass(是否直接代理目标类)为false
- 指定的目标类为接口类型
- 当且仅当使用
newProxyInstance
或getProxyClass
动态将目标类生成代理类时
- 使用Cglib代理
- proxyTargetClass(是否直接代理目标类)为true
- 指定的目标类不为接口类型
- 未使用
newProxyInstance
或getProxyClass
动态将目标类生成代理类时
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
- 在Spring5中,AOP默认还是用JDK动态代理,如果被代理类未实现接口才使用Cglib代理
- 而在Springboot2.x中,AOP已默认使用Cglib代理
即被代理类有没有实现接口,它都使用Cglib代理
那如何更改为JDK代理呢?在Springboot中都是依靠自动装配来实现的,在启动过程中会加载各种配置,其中就涉及了AOP相关的配置,通过spring.factories
可知相关配置代码在org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
。
从代码得知,Springboot2.x默认使用Cglib代理在于配置了spring.aop.proxy-target-class=true
,故要切为JDK代理可配置spring.aop.proxy-target-class=false
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
ClassProxyingConfiguration(BeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
}
使用类加载器创建代理对象
类加载器一般使用应用类加载器AppClassLoader
,也可以自己实现了自定义加载器。
Cglib代理
Cglib代理是委托给CglibAopProxy
去创建的,创建过程如下
- 获得目标类
rootClass
- 如果目标类已经被代理,则获取它的父类作为目标类
- 验证目标类,检查是否需要写日志
- 创建Enhancer对象(即代理增强器,类比于jdk自带的Proxy),准备创建代理类
- 设置目标类为代理增强器父类
- 设置要实现的接口,有则使用目标类的接口,默认还会实现
SpringProxy
,Advised
接口 - 覆盖命名策略,一般生成的代理类都是有对应的命名策略,在spring中,命名规范是:目标类名+
$$EnhancerBySpringCGLIB
- 设置生成字节码的策略,默认使用
DefaultGeneratorStrategy
,Spring中用了GeneratorStrategy
的另一种实现ClassLoaderAwareGeneratorStrategy
- 设置回调过滤器,根据被拦截的方法执行不同的处理逻辑。
CallbackFilter#accpet
在实际调用中,会根据被拦截的方法返回对应的索引,在Callback
中会根据索引值拿到对应的回调处理逻辑
- 创建目标类的代理类和目标类的代理类实例
- 创建代理类
proxyClass
- 创建代理类实例
proxyInstance
- 设置回调组
Callbacks
,和CallbackFilter
结合使用,回调组中有一个通用回调处理器DynamicAdvisedInterceptor
,其intercept()
是拦截的首入口。
- 创建代理类
public Object getProxy(@Nullable ClassLoader classLoader) {
try {
Class> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
Class> proxySuperClass = rootClass;
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
proxySuperClass = rootClass.getSuperclass();
// ...
}
validateClassIfNecessary(proxySuperClass, classLoader);
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
Class>[] types = new Class>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
": Common causes of this problem include using a final class or a non-visible class",
ex);
}
catch (Throwable ex) {
throw new AopConfigException("Unexpected AOP exception", ex);
}
}
JDK代理
JDK代理是委托给JdkDynamicAopProxy
去创建的,创建过程如下
- 设置要实现的接口,有则使用目标类的接口,默认还会实现
SpringProxy
,Advised
接口 - 查找目标类是否定义了
equals
和hashcode
,是的话分别标记equalsDefined
为true,hashCodeDefined
为true - 调用
Proxy.newProxyInstance
生成目标类的代理类实例
public Object getProxy(@Nullable ClassLoader classLoader) {
Class>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
事务传播机制
在Spring中,事务机制与AOP做了结合,通过AOP封装了事务管理的代码,接下来将会通过AOP理解事务的传播机制。
Propagation七种传播机制
相关枚举在org.springframework.transaction.annotation.Propagation
中
机制 | 说明 | 场景 |
---|---|---|
PROPAGATION_REQUIRED | 若线程上下文中存在事务则加入,不存在则创建一个新事务 | 最常使用,因为它是Spring缺省的传播机制 |
PROPAGATION_SUPPORTS | 若线程上下文中存在事务则加入,不存在则以非事务的模式执行 | -(使用场景较少) |
PROPAGATION_MANDATORY | 若线程上下文中存在事务则加入,不存在则抛出异常 | 常用于检测调用代码的上下文是否存在事务,提醒必须以事务运行 |
PROPAGATION_REQUIRES_NEW | 若线程上下文中存在事务则暂时挂起****,并创建一个新事务,执行完后才恢复其他上下文事务 | 常用于内层事务异常会导致回滚时,外层事务(一般)不会被回滚 |
PROPAGATION_NOT_SUPPORTED | 若线程上下文中存在事务则挂起,并以非事务的模式执行,执行完才恢复上下文事务 | 常用于减少事务范围,同时避免内层事务异常而导致不必要的全局回滚的场景 |
PROPAGATION_NEVER | 若线程上下文中存在事务,则抛出异常 | -(使用场景较少) |
PROPAGATION_NESTED | 若线程上下文中存在则嵌套事务执行,不存在则创建一个新事务。 它仅支持“在特定的事务管理器 DataSourceTransactionManager 上,以及使用jdbc3.0驱动程序”的使用,其提供了一个“save point”的父子事务的概念,在进入子事务之前建立一个“save point”,当子事务回滚时会先滚到“save point”,而父事务可以选择性回滚。 |
-(使用场景较少) |
实践
关于传播机制,在上面已经做了描述,然而事实真的如以上描述一般的简单吗,接下来模拟做些测试用例,看看结论。
首先,先清楚两个点
- Spring默认传播机制是
PROPAGATION_REQUIRED
-
@Transactional
中默认回滚异常是RuntimeException
,但是p3c建议我们显示的rollback
场景1:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出Runtime异常
@Transactional(rollbackFor = Exception.class)
public void addUser() {
userMapper.insert(User.create("Jerry", 21));
userChannelService.addUserChannel();
}
@Transactional(rollbackFor = Exception.class)
public void addUserChannel() {
throw new NullPointerException("在不同service内假装抛出Runtime异常");
}
场景2:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出Runtime异常但是进行了异常捕获
@Transactional(rollbackFor = Exception.class)
public void addUserV2() {
userMapper.insert(User.create("Jerry", 22));
userChannelService.addUserChannelV2();
}
@Transactional(rollbackFor = Exception.class)
public void addUserChannelV2() {
try {
throw new NullPointerException("在不同service内假装抛出Runtime异常并捕获");
} catch (NullPointerException e) {
}
}
以上是在内层事务捕获,同样考虑一下在外层事务捕获的结果
场景3:两个service A/B, A方法调用B方法且都开启了事务,B方法抛出Runtime异常但是进行了异常捕获,且B方法事务指定了隔离级别为Propagation.REQUIRES_NEW
@Transactional(rollbackFor = Exception.class)
public void addUserV3() {
userMapper.insert(User.create("Jerry", 23));
userChannelService.addUserChannelV3();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addUserChannelV3() {
try {
userChannelMapper.insert(UserChannel.create(1, "WX", "111"));
throw new NullPointerException("在不同service内假装抛出Runtime异常并捕获,但是我设置了隔离级别为新建事务");
} catch (NullPointerException e) {
e.printStackTrace();
}
}
以上是在内层事务捕获,同样考虑一下在外层事务捕获的结果
场景4:两个service A/B, A方法调用B方法且都开启了事务,B方法抛出Runtime异常,且B方法事务指定了隔离级别为Propagation.REQUIRES_NEW
@Transactional(rollbackFor = Exception.class)
public void addUserV4() {
userMapper.insert(User.create("Jerry", 24));
userChannelService.addUserChannelV4();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void addUserChannelV4() {
throw new NullPointerException("在不同service内假装抛出Runtime异常,但是我设置了隔离级别为新建事务");
}
场景5:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出自定义异常(非Runtime)
@Transactional(rollbackFor = RuntimeException.class)
public void addUserV5() throws CustomException {
userMapper.insert(User.create("Jerry", 25));
userChannelService.addUserChannelV5();
}
@Transactional(rollbackFor = RuntimeException.class)
public void addUserChannelV5() throws CustomException {
throw new CustomException("在不同service内假装抛出自定义异常");
}
以上指定回滚异常为RuntimeException,要是设置为Exception,会是什么结果呢?
场景6:两个service A/B, A方法调用B方法且都开启了事务(默认级别Propagation.REQUIRED),B方法抛出自定义异常(非Runtime)但是进行了异常捕获
@Transactional(rollbackFor = RuntimeException.class)
public void addUserV6() {
userMapper.insert(User.create("Jerry", 26));
userChannelService.addUserChannelV6();
}
@Transactional(rollbackFor = RuntimeException.class)
public void addUserChannelV6() {
try {
userChannelMapper.insert(UserChannel.create(1, "WX", "111"));
throw new CustomException("在不同service内假装抛出自定义异常并捕获");
} catch (CustomException e) {
e.printStackTrace();
}
}
以上指定回滚异常为RuntimeException,要是设置为Exception,会是什么结果呢?
场景7:service A, a方法调用内部方法b且都开启了事务(默认级别Propagation.REQUIRED),b方法抛出Runtime异常
@Transactional(rollbackFor = Exception.class)
public void addUserV7() {
userMapper.insert(User.create("Jerry", 27));
exception();
}
@Transactional(rollbackFor = Exception.class)
public void exception() {
throw new NullPointerException("在同个service内假装抛出异常");
}
场景8:service A, a方法调用内部方法b且都开启了事务(默认级别Propagation.REQUIRED),b方法抛出Runtime异常,且c方法事务指定了隔离级别为Propagation.REQUIRES_NEW
@Transactional(rollbackFor = Exception.class)
public void addUserV8() {
userMapper.insert(User.create("Jerry", 28));
exceptionV2();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void exceptionV2() {
throw new NullPointerException("在同个service内假装抛出异常,但是我设置了隔离级别为新建事务,也会回滚");
}
AopProxy代理事务的操作过程
上面提供了8个场景,在获取测试结果之前,先看下AOP代理事务的操作流程
Cglib代理过程
同样,Cglib代理过程也是委托给CglibAopProxy
去执行的,那结合上面创建代理对象的流程,我们如何得知代理执行的入口和代理流程呢?
在Cglib代理对象的创建过程中设置的回调组Callbacks
中,回调类如下
Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
Callback[] mainCallbacks = new Callback[] {
aopInterceptor, // for normal advice
targetInterceptor, // invoke target without considering advice, if optimized
new SerializableNoOp(), // no override for methods mapped to this
targetDispatcher, this.advisedDispatcher,
new EqualsInterceptor(this.advised),
new HashCodeInterceptor(this.advised)
};
结合前面提到的,它一般和CallbackFilter
结合使用,CglibAopProxy#accpet
在实际调用中,会根据被拦截的方法返回对应的索引,在Callback
中会根据索引值拿到对应的回调处理逻辑,其实CglibAopProxy
中已经声明了对应静态不可变的成员变量
private static final int AOP_PROXY = 0;
private static final int INVOKE_TARGET = 1;
private static final int NO_OVERRIDE = 2;
private static final int DISPATCH_TARGET = 3;
private static final int DISPATCH_ADVISED = 4;
private static final int INVOKE_EQUALS = 5;
private static final int INVOKE_HASHCODE = 6;
在org.springframework.cglib.proxy.Enhancer#emitMethods
中,会遍历被代理类的方法,并且设置切入点,初始默认会返回AOP_PROXY
,即回调处理逻辑将在通用AOP回调DynamicAdvisedInterceptor#intercept
中进行,具体看源码即可
好了,知道了代理的入口,再整理下代理流程
- 调用方法
method
,首先拦截于DynamicAdvisedInterceptor#intercept
; - 获取目标类实例
targetClass
,根据method
+targetClass
从代理配置管理器AdvisedSupport
中获取拦截器(Advice通知)链条chain
; - 创建一个Cglib方法调用对象
CglibMethodInvocation
,执行调用方法proceed
,准备调用拦截器链条 -
CglibMethodInvocation
中维护着当前拦截器索引currentInterceptorIndex
,当它小于拦截器链条长度时,会自增并依次作为索引条件获取指定拦截器,并执行它的切入点TransactionInterceptor#invoke()
,从名字可以看出是一个事务操作的拦截器,当自增到等于拦截器链条长度时,开始真正调用被代理方法; - 接下来会执行
TransactionAspectSupport#invokeWithinTransaction
,在被代理方法前先开启事务,接着回调到上一步,循环执行拦截器;
事务执行过程
在Spring事务执行中,它提供了抽象事务管理器类
AbstractPlatformTransactionManager
来处理通用的事务处理流程,而它的子类将做具体的实现,如事务开始,事务提交、事务回滚等,如DataSourceTransactionManager
、JpaTransactionManager
等。
上面代理执行过程中,在事务拦截器阶段,将会执行一段事务操作,其中最核心的过程和代码片段如下
- 开启事务
- 执行被代理的业务方法
- 异常则回滚事务
- 提交事务
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//...
commitTransactionAfterReturning(txInfo);
return retVal;
}
执行过程很清晰,值得注意的是,在completeTransactionAfterThrowing
中,并不一定直接回滚,如果发现当前异常非指定的rollback异常,则会直接commit()
,再将异常抛到外部,你觉得此 commit()
真的会成功吗?看下面场景5的结果吧!
再看下回滚事务的代码,位于抽象事务管理器AbstractPlatformTransactionManager#processRollback
,有下面几种回滚原因
- 原因一:如果事务设置了“save point”,即使用了
PROPAGATION_NESTED
嵌套事务的传播机制,则回滚到“save point”
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
- 原因二:如果事务是新开事务(单一事务也算),则直接让该事务回滚
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
- 原因三:如果当前事务有回滚标志,或者当前事务开启了全局事务回滚
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
- 原因四:以上几种都属于上述步骤的第三步:异常则回滚事务,即在
commit()
之前就导致的异常回滚,而当在请求commit()
时检测到全局事务被标记了rollback-only
,这是一种不期望的行为,也会导致回滚
commit()
过程的回滚代码,位于AbstractPlatformTransactionManager#commit
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
在AbstractPlatformTransactionManager#processRollback
中,会直接打印出错误信息:不期望的回滚异常
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
由于这里使用的具体实现是DataSourceTransactionManager
,故实际的回滚操作doRollback
、doSetRollbackOnly
将会在其中操作。
结合代理操作过程得出实践场景结果
场景1(默认级别Propagation.REQUIRED)
结果:A/B都会回滚(从上一步分析得知,这里属于原因三的回滚行为)
总结:A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,则B方法会回滚,而因为和A方法处于同一事务,也导致A方法会回滚。
场景2(默认级别Propagation.REQUIRED)
-
结果:
- 在A方法捕获:A、B会回滚(从上一步分析得知,这里属于原因四的回滚行为)
- 在B方法捕获:A、B不会回滚
-
总结:
- A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在外层A方法捕获,则B方法会回滚,而因为和A方法的同一事务,也导致A方法会回滚(不管A方法是否有做捕获操作),此时会抛出"Transaction rolled back because it has been marked as rollback-only"
- A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在内层捕获,则B方法不会回滚,A方法也不会回滚
场景3(A:默认级别Propagation.REQUIRED,B:Propagation.REQUIRES_NEW)
-
结果:
- A方法一定不会回滚
- 在A方法捕获:B会回滚(从上一步分析得知,这里属于原因二的回滚行为)
- 在B方法捕获:B不会回滚
-
总结:
- A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在外层A方法捕获,则B方法会回滚,但是因为和A方法不是同一事务,所以A方法不会回滚
- A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,并在内层B方法捕获,则B方法不会回滚,A方法也不会回滚
场景4(A:默认级别Propagation.REQUIRED,B:Propagation.REQUIRES_NEW)
- 结果:A/B方法都会回滚(从上一步分析得知,这里B方法的回滚属于原因二的回滚行为)
可能有些人会有疑问,我已经将B方法设置为开启事务,怎么还会导致A方法回滚呢?
很简单,从A方法看起,它指定回滚的异常是Exception
,而B方法抛出了NPE异常后又没有捕获,所以尽管B方法开启了新的事务,但是是由A发起的且抛出NPE异常,所以A方法也回滚了。
故可以换另一种角度,当A方法指定回滚异常是RuntimeException
,而B方法回滚异常为非Runtime异常,此时可以发现A方法并不会回滚!并且B方法指定回滚异常也为RuntimeException
,则B方法也不会回滚!
- 总结:A类方法调用B类方法,且都开启事务,若B类方法抛出Runtime异常,则A、B方法都会回滚
场景5(默认级别Propagation.REQUIRED)
- 结果:两个方法同时指定回滚异常为RuntimeException,才不会回滚,否则都会回滚
在上面事务执行过程中,讲到了当前异常非rollback异常时会直接commit()
,而此次commit()
会不会成功呢?答案是不一定!
如果A/B都在同个事务中,并且内层B方法指定异常为RuntimeException
,而抛出异常为自定义异常,则会commit()
,由于没有捕获异常,故无论外层A方法指定什么异常,A/B方法的业务操作都会一起被回滚!(从上一步分析得知,这里的回滚属于原因二的回滚行为);那怎样才能让此次commit()
成功呢,只要在内层B方法中进行捕获即可
- 总结:
- A类方法(回滚异常为Exception)调用B类方法(回滚异常为RuntimeException),且都开启事务,若B类方法抛出自定义异常(非Runtime),则A、B都会回滚;
- A类方法(回滚异常为Exception)调用B类方法(回滚异常为Exception),且都开启事务,若B类方法抛出自定义异常(非Runtime),则A、B都会回滚;
- A类方法(回滚异常为RuntimeException)调用B类方法(回滚异常为Exception),且都开启事务,若B类方法抛出自定义异常(非Runtime),则A、B都会回滚;
- A类方法(回滚异常为RuntimeException)调用B类方法(回滚异常为RuntimeException),且都开启事务,若B类方法抛出自定义异常(非Runtime),则B会回滚,A不会回滚
场景6(默认级别Propagation.REQUIRED)
- 结果:不会回滚
- 总结:A类方法调用B类方法,且都开启事务,若B类方法抛出自定义异常并且捕获,则不会回滚
场景7(默认级别Propagation.REQUIRED)
- 结果:会回滚(从上一步分析得知,这里的回滚属于原因二的回滚行为)
- 总结:在同个类中a方法调用b方法,b方法不会开启新事务,即不会用到事务的传播机制
场景8(默认级别Propagation.REQUIRED)
- 结果:会回滚(从上一步分析得知,这里的回滚属于原因二的回滚行为)
- 总结:在同个类中a方法调用b方法,b方法无论设置什么都不会开启新事务,即不会用到事务的传播机制