在日常的web开发中,我们经常会用到数据库的事务,spring中也提供了相应的方法来进行事务的管理。其中,Spring的注解@Transactional是我们常用的管理数据库事务的方法,下面介绍一下@Transactional的原理及使用。
@Transactional实现事务管理的原理:
Spring在创建bean对象时,对于带有 @Transactional注解的类,会通过AOP创建一个增强的对象(即BeanPostProcessor 的 postProcessAfterInitialization 方法),比如ServiceA中含有注解@Transactional,spring的IOC流程中,会先根据ServiceA的构造函数创建对象serviceA,然后在初始化时,会调用postProcessAfterInitialization方法创建serviceA的代理对象serviceAProxy,最后Spring的bean工厂存放的对象是serviceA的代理serviceAProxy,这个ServiceAPlus会对原先的方法进行增强, 这样当其它类在创建对象,如果属性中含有ServiceA,在添加属性的时候,加入的属性是serviceAProxy而不是serviceA,如
class ServiceA{
@Transactional
int funcA(){
.......
}
int funcB(){
funcA();
}
}
Class ServiceB{
@autowired
ServiceA serviceA;
}
这个时候创建的对象serviceB中包含的ServiceA属性是serviceAProxy。当serviceB调用ServiceA中的方法funcA时,不是直接调用,而是调用serviceAProxy中的invoke方法(以JDK为例),在invokde方法中,会添加一些数据库相关的操作,如:
//看是否有必要创建一个事务,根据事务传播行为,做出相应的判断
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
//回调方法执行,执行目标方法(原有的业务逻辑,即ServcieA.funcA())
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清除信息
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
这样也就解释了为什么自调用的问题,即在ServiceA中,方法funcB调用funcA,注解@Transactional会失效,因为这个时候是直接调用,而不是通过invoke方法调用代理对象,所以无法对原先的方法增强。
Spring中的事务的传播行为有以下几种:
//事务传播行为类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
int PROPAGATION_REQUIRED = 0;
//事务传播行为类型:支持当前事务,如果当前没有事务,就以非事务方式执行。
int PROPAGATION_SUPPORTS = 1;
//事务传播行为类型:当前如果有事务,Spring就会使用该事务;否则会抛出异常
int PROPAGATION_MANDATORY = 2;
//事务传播行为类型:新建事务,如果当前存在事务,把当前事务挂起。
int PROPAGATION_REQUIRES_NEW = 3;
//事务传播行为类型:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
int PROPAGATION_NOT_SUPPORTED = 4;
//事务传播行为类型:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
int PROPAGATION_NEVER = 5;
//事务传播行为类型:如果当前存在事务,则在嵌套事务内执行。
int PROPAGATION_NESTED = 6;
这些传播行为中,有些行为会需要开启一个新的事务(如PROPAGATION_NESTED),这个时候就涉及到了线程上下文的操作,流程如下:
//挂起上层事务
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
//开启本层新事务
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);//也是true
//创建txStatus对象 isNewTransaction是true
DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//执行conn.setAutoCommit(false) 以及ConnectionHolder状态设置,以及ConnectionHolder线程绑定
doBegin(transaction, definition);
//TransactionSynchronizationManager属性填充
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException beginEx) {
//如果出异常 就恢复上层事务
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
catch (Error beginErr) {
resumeAfterBeginException(transaction, suspendedResources, beginErr);
throw beginErr;
}
如上,对于需要开启新事务的函数,需要将上层的事务挂起,将外层事务的相关变量(如数据库连接、事务状态等)保存下了,然后清除掉。本层事务重新连接数据库、开始新的事务。当本层事务处理完成后,再将之前保存的相关变量恢复。这样就实现了开启新事务的功能。