一背景:先看如下代码,然后思考问题
@Service public class MyService { public void doTest(){ this.doTest2(); } @Transactional public void doTest2(){ this.doTest3(); } @Transactional(timeout = 20) public void doTest3(){ this.doTest4(); } @Transactional(timeout = 30,propagation=Propagation.REQUIRED) public void doTest4(){ System.out.println("ddddddddddddddddd"); } } @Configuration @ComponentScan("com.yang.xiao.hui.aop") @EnableTransactionManagement public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class); MyService service = ctx.getBean(MyService.class); service.doTest(); service.doTest2(); } }
思考1:如果我接着调用doTest()方法,事务会生效么?
思考2:如果我调用doTest2(),那么事务是以doTest2的为准,还是doTest3的为准,还是doTest4的为准
思考3:贴了@Transactional注解就一定要配置数据库么?
思考4: 事务的传播级和隔离级别怎么配置?
二 .源码分析:
我们贴了一个@Transactional,事务就生效,那么该方法肯定被拦截了,所以会存在一个特殊的方法拦截器,MethodInterceptor,我们唯一的入口就是在主启动类贴了一个@EnableTransactionManagement注解,那么我们就从这里入手,看该注解做了
啥?
通过上图,可知道,一个简单的注解就向spring容器中注入了那么多的类,我们关注的类是ProxyTransactionManagementConfiguration
我们看看该类的源码:
通过上图,我们找到了一个事务拦截器,看看它的继承体系:
它就是一个方法拦截器,方法执行时会拦截目标方法,那么我们的事务操作肯定都是在这个拦截器的拦截方法里,debug调试该方法即可
继续根进:
@Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource(); //事务属性来源,啥意思呢?就是说哪些方法贴有事务注解,类似一个map将所有贴有事务注解的方法存了起来 final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);//通过指定的目标方法,去找到该方法贴的事务注解的信息,注解里面有很多参数的 final PlatformTransactionManager tm = determineTransactionManager(txAttr); //通过事务注解配置的属性来决定使用什么样的事务管理器 final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);//给这个方法启个唯一标识 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { //默认tm都是不属于CallbackPrefering....,所以会进入下面的逻辑 // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); //通过事务管理器,事务信息对象创建事务对象,在这一步将事务对象放到了ThreadLocal中 Object retVal; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); //开始执行目标方法 } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); //抛异常事务回滚 throw ex; } finally { cleanupTransactionInfo(txInfo); //清空这次事务信息,也就是从ThreadLocal中移除 } commitTransactionAfterReturning(txInfo); //如果没有抛异常就事务回滚 return retVal; } else { final ThrowableHolder throwableHolder = new ThrowableHolder(); //另一个分支 // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. try { Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> { TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try { return invocation.proceedWithInvocation(); //执行目标方法 } catch (Throwable ex) { if (txAttr.rollbackOn(ex)) { //判断是否配置的异常,是就抛出去 // A RuntimeException: will lead to a rollback. if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new ThrowableHolderException(ex); } } else { // A normal return value: will lead to a commit. throwableHolder.throwable = ex; return null; } } finally { cleanupTransactionInfo(txInfo); } }); // Check result state: It might indicate a Throwable to rethrow. if (throwableHolder.throwable != null) { throw throwableHolder.throwable; } return result; } catch (ThrowableHolderException ex) { throw ex.getCause(); } catch (TransactionSystemException ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); ex2.initApplicationException(throwableHolder.throwable); } throw ex2; } catch (Throwable ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); } throw ex2; } } }
上述方法非常清楚的展示了事务的执行原理:
接着详细分析上面的每个流程:
我先看看事务管理器接口的构成:该接口提供了事务提交方法,和事务的回滚方法
因此,我们先分析最简单的事务回滚和事务提交方法:
1.completeTransactionAfterThrowing(txInfo, ex);事务回滚方法
由源码知道,只有指定了哪些异常要回滚,事务才会回滚,否则事务就直接提交,但我们也没指定过,因此有必要看看txInfo.transactionAttribute.rollbackOn(ex)的逻辑
默认没有配置指定异常,事务回滚也只会针对RuntimeException或者Error
2. 接下来看看事务提交逻辑:commitTransactionAfterReturning(txInfo);
由此看来,事务管理器是一个非常重要的接口,但他仅仅是个接口,具体事务怎么提交或者回滚,完全是由接口的实现类来决定,那么,我们就不一定需要是数据库,我们也可以针对redist,mogodb等来实现该接口,进
行对应的事务提交或者回滚,因此,开头提出的思考3:贴了@Transactional注解就一定要配置数据库么?答案就显而易见了
既然事务是由事务管理器来决定的,它是一个接口,那它是如何决定使用哪个事务管理器的呢?下面我们分析事务管理器的获取过程:
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
@Nullable protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) { // Do not attempt to lookup tx manager if no tx attributes are set if (txAttr == null || this.beanFactory == null) { return getTransactionManager(); //获取默认的事务管理器,由于没有配置,所以取不到 } String qualifier = txAttr.getQualifier(); //如果我们在事务注解上指定了事务管理器的beanName,就从spring容器中或者 if (StringUtils.hasText(qualifier)) { return determineQualifiedTransactionManager(this.beanFactory, qualifier); } else if (StringUtils.hasText(this.transactionManagerBeanName)) { //如果前面不满足,本类配置事务管理器的BeanName就以其为准 return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName); } else { PlatformTransactionManager defaultTransactionManager = getTransactionManager(); //上面的都不满足,就获取默认事务管理器 if (defaultTransactionManager == null) { defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY); //没有的话,就从缓存中获取 if (defaultTransactionManager == null) { defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class); //缓存中没有的话,就直接从容器中获取PlatformTransactionManager this.transactionManagerCache.putIfAbsent( DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager); //将获取到的事务管理器设置到缓存中 } } return defaultTransactionManager; } }
上述的获取过程非常的简单:如果我们自己指定了事务管理器,就用我们自己的,没有指定就从spring容器获取默认的;这样我们是可以创建自己的事务管理器,然后配置给指定的事务:如
最后看看我们配置的事务注解属性是如何被封装成对象的:先看看事务注解都有哪些属性:
public @interface Transactional { @AliasFor("transactionManager") String value() default ""; //指定事务管理器的BeanName @AliasFor("value") String transactionManager() default ""; //指定事务管理器的beanName Propagation propagation() default Propagation.REQUIRED; //事务的传播级别,面试时问spring事务的传播级别,答案就在这里了 Isolation isolation() default Isolation.DEFAULT; //事务的隔离级别 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; //事务的超时时间 boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; //异常时,哪些异常需要回滚 String[] rollbackForClassName() default {}; //异常时,哪些异常需要回滚 Class<? extends Throwable>[] noRollbackFor() default {};//异常时,哪些异常不需要回滚 String[] noRollbackForClassName() default {};//异常时,哪些异常不需要回滚 }
上述的属性最终会被封装成:TransactionAttribute,回到最初拦截的源码:
一直跟进去后,可以看到:
可以看到,很多属性是跟数据库有关的,事务的隔离级别,传播级别等,他们实现的事务管理器是DataSourceTransactionManager,有兴趣的可以了解该事务管理器的实现,就可以知道事务的传播级别原理了
三.问题解答:
问题1:直接调用doTest方法,doTest方法没有事务,然后doTest方法调用doTest2,doTest2有事务,那么doTest2的事务能生效么?debug在方法拦截器里
执行结果发现,debug走不到事务拦截器那里,看看控制台的打印:
由此得出结论,同一个类中,非事务方法调用事务方法,会导致事务失效,原因如下:
service.doTest();虽然service是代理类,但真正执行doTest时,是代理类中的被代理对象调用的
既然我们知道了,doTest2并不是被代理类service直接调用的,那么如果我们就是使用service直接调用不就可以了么?我们第一能想到的是作为参数传入,改造如下
启动再次测试:
由此看到,事务生效了,那还有没更好的方案呢,因为这样传感觉不方便,代码回滚到之前,在service.doTest() debug调试
引出来2个问题:1.条件如何设置,AopContext存代理对象到哪里
由此可知,设置到了ThreadLocal中了,那条件又是啥:exposeProxy,该属性在@EnableAspectJAutoProxy(exposeProxy = true)中,于是我们改造代码:
至此:当一个类中非事务方法,调用事务方法时,不生效的解决方案有上面2种;
最后一个问题:
多个事务方法直接的链式调用,到底以哪个事务为准,这里生效的前提是数据库的事务管理器,或者对事务传播级别做处理的事务管理器:这里就可以顺便了解下事务传播级别了
PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。
PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常
PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行
我们以默认的事务传播级别来解答:
PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。例如doTest2()——》doTest3(),对于doTest3()来说,如果它配置的事务隔离级别是REQUIRED,那么doTest3就会以doTest2配置的事务来执行
PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。例如doTest2()——》doTest3(),对于doTest3()来说,如果配置了该级别,doTest2配置了事务就使用,doTest2没有活动的事务就抛异常
其他的隔离级别以此类推。。。。。。。。。。。。。