总结:Spring的事务本质上就是操作数据库的事务
一、Spring事务流程源码剖析
Spring的声明式事务处理是通过AOP切面实现的核心逻辑在TransactionAspectSupport#invokeWithinTransaction中(这里不在赘述代理类如何生成以及切面何时进入,如有疑问可以参考SpringAOP之代理对象的创建、SpringAOP之代理对象的执行、Spring声明式事务控制原理之声明式事务的重要组件(注解实现)以及之间关系、Spring声明式事务控制原理之声明式事务的重要组件在AOP中的应用)
invokeWithinTransaction中三个主要步骤:
1.createTransactionIfNecessary 获取当前事务
2.completeTransactionAfterThrowing 发生异常时回滚
3.commitTransactionAfterReturning 无异常时提交
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
// 获取事务属性解析器ProxyTransactionManagementConfiguration中注入
TransactionAttributeSource tas = getTransactionAttributeSource();
// 当前方法上声明式事务的相关属性信息
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)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 获取事务详情,开启事务(包含当前事务全部信息,以及上层挂起事务)
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
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);
}
// 如果目标方法正常执行执行事务提交操作。
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.createTransactionIfNecessary 获取当前事务流程分析
2.completeTransactionAfterThrowing 发生异常时回滚流程分析
3.commitTransactionAfterReturning 无异常时提交流程分析
二、传播行为运用案例
1.经典案例—回滚标记
out_method_required1{
method_required_1(){
保存数据
}
try{
method_required_2(){
保存数据
throw new RuntimeException;
}
}catch{
吃掉异常
}
}
问:method_required_1中数据可以保存成功嘛?
答:不会保存成功会一起回滚掉,因为method_required_2回滚时标记了ConnectionHolder为rollbackOnly = true;
2.经典案例2—REQUIRES_NEW传播行为运用
out_method_required2{
method_required_1(){
保存数据
}
try{
method_requires_new_2(){
保存数据
throw new RuntimeException;
}
}catch{
吃掉异常
}
}
问:method_required_1会保存成功嘛?
答:会保存成功,因为method_requires_new_2事务的传播行为是REQUIRES_NEW类型,此类型的传播行为会挂起当前事务,重启一个新事务,与之前事务没有任何关系当新建事务执行完毕后在回复挂起的事务。通过操作TransactionSynchronizationManager中的ThreadLocal实现(数据库连接与线程的绑定关系)。
3.经典案例3—NESTED传播模式运用
out_method_required3{
try{
method_nested_2(){
保存数据
throw new RuntimeException;
}
}catch{
吃掉异常
}
method_required_1(){
记录日志
}
}
问:试问method_required_1会保存成功嘛?
答:会保存成功,因为method_nested_2的传播类型是NESTED类型,在触发回滚时会利用保存点进行回滚+清除保存点,不会标记ConnectionHolder为只能回滚状态导致使用当前连接的全部事务回滚;在触发Commit时会清除保存点。故不会导致全局回滚。
4.经典案例4—NESTED传播模式运用2
out_method_required3{
method_nested_1(){
记录日志
}
try{
method_required_2(){
保存数据
throw new RuntimeException;
}
}catch{
吃掉异常
}
}
问:method_nested_1会保存成功嘛?
答:不会保存成功,method_nested_1采用NESTED的传播模式,当前模式下commit操作会清除保存点,但不会真正操作Commit,会跟随外层事务一起提交回滚,method_required_2采用的是REQUIRED传播模式,发生回滚是会标记ConnectionHolder为只能回滚状态,导致外层事务也会回滚从而导致method_nested_1回滚保存失败。
三、心得
在内层事务的视角解读三种传播行为:
1.REQUIRED是成功了与外层事务一起Commit或着一起Rollback,失败了标记使用当前连接的事务未RollbackOnly=true状态,会导致使用当前Connection连接的事一起回滚包括外层事务。
2.REQUIRES_NEW是挂起外层事务,Commit和Rollback都与外层事务无关,只自己提交或者回滚,当回滚或提交结束后会回复外层挂起事务;
3.NESTED是成功了与外层事务一起Commit或着一起Rollback,失败了自己回滚不影响外层事务;
NESTD和REQUIRES_NEW运用场景
如果存在"业务逻辑成功需要记录日志,失败日志一起回滚,且日志记录异常不能影响业务逻辑"这种需求。
建议业务逻辑使用REQUIRED传播行为放在前面,记录日志使用NESTED传播行为放在后面。
不建议使用REQUIRES_NEW这种传播行为,因为当执行REQUIRES_NEW传播行为的事务横切逻辑时会多开一个Connection连接,比起NESTED更占用资源。