springboot 事务执行全流程分析
springboot 事务执行全流程分析
在上篇文章springboot 事务创建流程源码分析中主要讲了springboot事务创建的过程,本次我们来看看事务具体执行的过程。
这里关于几个名称提前先达成一致:
com.springboot.transaction.service.impl.UserServiceImpl这个类我们称它为原始类,它的对象我们称原始对象
springboot通过aop生成的com.springboot.transaction.service.impl.UserServiceImpl的子类,我们称它为代理类,它的对象我们称代理对象
在main方法中会有这一句
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,p);
这里的p是设置的springboot动态生成代码的本地保存位置,在生成代理类中,我们就可以在这里找到对应的所有动态代理类的class文件,通过反编译,我们也能了解更多的信息为了避免其他框架对事务执行的干扰,本篇文章数据库使用的是原生的jdbctemplate。主要是其他框架,类似mybatis之类,在事务过程中也会插入自己框架的代码。
本篇博客的源码路径:https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-jdbc-transaction
1. 事务方法执行前的准备工作
在服务启动后,我们在浏览器中输入http://localhost:8080/addUser来请求后台。就会请求到com.springboot.transaction.jdbc.controller.UserController的addUsers方法,具体会执行到return userService.addUser();
这句代码,这里的userService就是上篇文章中讲到的代理类,这就会调用到代理类中的addUser方法。
上篇文章我们也贴出来了反编译的userService代理类,具体会调到下面的代码
//代理类中的方法
public final boolean addUser() {
//这里的this.CGLIB$CALLBACK_0,就是上篇文章中讲到的在最终生成代理类后调用setCallbacks方法赋值的
//这里的this.CGLIB$CALLBACK_0就是CglibAopProxy$DynamicAdvisedInterceptor
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
//CGLIB$addUser$0$Method是原始类的方法public boolean com.springboot.transaction.jdbc.service.impl.UserServiceImpl.addUser()
//CGLIB$addUser$0$Proxy是MethodProxy类的对象,最终的调用就是通过MethodProxy.invoke来完成的
if (var10000 != null) {
Object var1 = var10000.intercept(this, CGLIB$addUser$0$Method, CGLIB$emptyArgs, CGLIB$addUser$0$Proxy);
return var1 == null ? false : (Boolean)var1;
} else {
return super.addUser();
}
}
上面的代码就会调用到CglibAopProxy$DynamicAdvisedInterceptor的intercept方法,我们继续进到这个里面去看看
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
//这里会获取我们的原始对象和原始类
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
//这里会获取具体需要执行拦截的拦截器列表,我们这里只会有一个org.springframework.transaction.interceptor.TransactionInterceptor的对象,这个也是在上篇文章中有讲到,是作为一个bean对象加载的
//这里的this.advised是org.springframework.aop.framework.ProxyFactory的对象,关于这个类在上篇文章中有过描述,这里就不再说类
//我们先进到这个方法里面去看看拦截器列表是如何获取到的
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
//在这里就会去生成CglibMethodInvocation对象,并通过调用process来完成整个事务过程的处理的。
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
//在这里会对返回的结果做个简单的判断,因为原始类型不能为null,这里会判断方法的返回类型是不是原始类型,当前的返回值是不是null,如果两者都是就抛出异常
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
下面我们看看this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);这句代码具体的执行情况
//这个方法是在AdvisedSupport类中
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
//这里会对方法的拦截器列表做个缓存
//当前这里缓存中是没有的,所以会走到if分支中
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
//我们进到这个分支中看看
//这里的this.advisorChainFactory是一个成员变量,在声明的时候就初始化了,之后没有调用set方法重新赋值 //advisorChainFactory = new DefaultAdvisorChainFactory();
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
//获取到对应的拦截器列表后,加到缓存中,下次就可以直接获取到
this.methodCache.put(cacheKey, cached);
}
return cached;
}
看看this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass)这句的执行过程
//这个方法是DefaultAdvisorChainFactory
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) {
// This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
//这里的advisors数组中只有一个对象,BeanFactoryTransactionAttributeSourceAdvisor。这个类上篇文章中也就有讲,这里也就不再说了
Advisor[] advisors = config.getAdvisors();
List<Object> interceptorList = new ArrayList<>(advisors.length);
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
Boolean hasIntroductions = null;
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
......//这里都是判断方法是否和advisors是否匹配,和上篇文章中的匹配步骤基本也是一致的,这里也就略过了
if (match) {
//如果能够匹配,就会从advisor中获取方法的拦截器列表,加到list中最终返回
//getInterceptors这个方法也比较简单,就是直接通过advisor.getAdvice()获取拦截器
//不过这个方法里面有一些其他的知识点,所以我们还是进去一起看看
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
//后面的代码基本都走不到,这里就直接略过了
......
return interceptorList;
}
//DefaultAdvisorAdapterRegistry中的方法
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
//这里的advice就是TransactionInterceptor了,上篇文章也讲过了
//Advice对应我们中文的术语好像叫通知,感觉好别扭
Advice advice = advisor.getAdvice();
//TransactionInterceptor实现了 MethodInterceptor,所以会走到这个if分支里面
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
//这里的adapters有3个对象,分别是new MethodBeoreAdviceAdapter(),new AfterReturningAdviceAdapter(),new ThrowsAdviceAdapter()
//这里也可以看到,aop的拦截(Advice通知)主要分为4种
//MethodInterceptor 是环绕通知,就是方法执行前后都会进行拦截处理
//MethodBeforeAdviceAdapter 前置通知,就是先执行拦截,后执行原始的方法;这种主要是对原始方法执行的参数做一些处理
//AfterReturningAdviceAdapter 后置通知,就是先执行原始的方法,后执行拦截;这种主要是对原始方法的执行结果做一些处理
//ThrowsAdviceAdapter这种就是如果原始方法执行抛出了异常,会由它来处理
//这4种拦截都是没有继承关系的,所以不会走进这个for if分支中
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}
下面我们再次回到CglibAopProxy类的intercept方法中看看retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
首先会对CglibMethodInvocation对象进行创建,这个就不去看了,都是简单的赋值操作,我们重点看看proceed方法的执行
//proceed会直接通过 super.proceed();调用父类的方法,我们直接看它父类的方法吧
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
//初始化currentInterceptorIndex=-1;this.interceptorsAndDynamicMethodMatchers.size()=1
//所以依次会从interceptorsAndDynamicMethodMatchers中获取拦截器来执行,执行完所有拦截器后执行invokeJoinpoint
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
//依次获取拦截器
//我们拦截器列表中只有一个TransactionInterceptor
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
//不会走到这个分支
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
.......
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
//会走到这里,走到TransactionInterceptor.invoke方法
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
//这里会得到原始类
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
//看这个方法的名字就能明白,"在事务内调用"
//这里的invocation就是上面的CglibMethodInvocation,可以看到proceedWithInvocation方法中又会重新调到proceed方法,就是我们上面说的递归调用了
//我们继续进到invokeWithinTransaction里面去看看,invokeWithinTransaction方法在它的父类TransactionAspectSupport中
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
invokeWithinTransaction这个方法的内容比较多,可以说事务内部处理都是在这个方法中完成的
//这个方法是在TransactionAspectSupport
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
//这个属性是在创建TransactionInterceptor这个bean时调用set方法赋值的,上篇文章中有讲到
TransactionAttributeSource tas = getTransactionAttributeSource();
//这个是获取到对应方法的事务的属性信息,这个在执行创建事务的时候就已经有获取,所以这里可以直接从缓存中获取到。这个上篇文章中也讲到了
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//这个是获取到事务管理器,第一次也是没有的,会从beanfactory中获取并加到缓存中,这里获取到的JdbcTransactionManager的对象
//具体是在DataSourceTransactionManagerAutoConfiguration中创建的,具体的创建过程和上篇文章讲到的其他bean基本意识一致的
final TransactionManager tm = determineTransactionManager(txAttr);
//下面的分支条件不成立,不会进入,我们也就不看了,这部分代码也就直接省略掉了
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
......
}
//这里主要是判断类型,如果是PlatformTransactionManager类型,就强转;如果不是就泡异常。
//JdbcTransactionManager间接实现了PlatformTransactionManager,这里是可以强转的
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
//这个是得到连接点的标识:具体就是全类名+.+方法名拼接个字符串。
//我们事务注解都是针对方法的,这个主要是用来如果对应事务属性中事务的名字为空,就会用这个来代码对应方法的事务属性的名字,可以通过这个来区分不同的事务
//我们这里是com.springboot.transaction.service.impl.UserServiceImpl.findAll
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//下面的条件是成立,会进到if分支里面去
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
//看这个方法名就知道这里就是根据需要来创建事务,具体那些场景需要创建事务,那些场景不需要呢?
//对springboot事务概念不了解的可以先看看之前写的关于springboot事务概念的一些简单介绍 https://www.cnblogs.com/wbo112/p/15427078.html
//我们具体进到这个方法去看看
TransactionInfo txInfo = createTransactionIfNecessary(ptm, 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.
//在上面创建完TransactionInfo后在这里会调用proceedWithInvocation,继续递归调回到proceed,最后调到invokeJoinpoint方法,在这个里面最终调用到this.methodProxy.invoke(this.target, this.arguments);中。关于methodProxy我们会在文章的最后做分析
//我们@Transactional注解的方法最终会在这里面执行
retVal = invocation.proceedWithInvocation();
//后面的代码都是根据@Transactional注解的方法执行是否抛出异常,决定后续是否进行事务提交还是回滚操作,这部分代码我们在后面单独说
//TransactionAspectSupport的方法
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
//我们这里事务默认是没有名字的,这里就会对事务属性进行包装,通过上一步生成的joinpointIdentification来作为事务的名字
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
//在这里,就会具体的创建事务,这个方法是在AbstractPlatformTransactionManager,我们进去看看
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
//在这里会创建TransactionInfo对象,并通过ThreadLocal的方式绑定到当前线程
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
//AbstractPlatformTransactionManager的方法
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
//这里的definition已经在上一步通过匿名内部类的方式包装成了DelegatingTransactionAttribute的子类
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
//这里会创建一个datasource的事务对象,并获取当前上下文的数据库连接,如果没有连接的话获取到就是null,我们进去这个方法看看
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
//这里会判断ConnectionHolder是不是null,如果ConnectionHolder不是null,还要判断是不是事务已经启动,如果已经启动了事务,就是已启动的事务中进行后续的执行
//我们这里ConnectionHolder是null,不会进入这个分支
//注意:这里的ConnectionHolder是区分不同DataSource的,只有同一个DataSource的当前ConnectionHolder才会走到这里
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
// Check definition settings for new transaction.
//这里是判断事务的超时时间,默认是-1,表示不超时。如果<-1,那就抛出异常
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
//后面就是根据事务的传播行为去进行处理了,我们这次是默认的传播行为,也就是Propagation.REQUIRED;所以会走到后面的else if分支中
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
//对于同一个DataSource,如果当前有开启事务就会从上面isExistingTransaction走进去,不会走到这里,所以这里针对的是不同的DataSource的,我们当前只有一个DataSource,这里面不会执行什么,就不进去了
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
//会在这里开启事务,我们进去看看
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
//DataSourceTransactionManager中的方法
@Override
protected Object doGetTransaction() {
//首先创建一个DataSourceTransactionObject,这个类主要是为类持有connection
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
//设置是否允许嵌套
txObject.setSavepointAllowed(isNestedTransactionAllowed());
//obtainDataSource()获取到的就是底层的DataSource,具体是什么不用关注;这个是在创建JdbcTransactionManager的时候作为bean的参数传入的。springboot中默认的数据库连接池是Hikari,所以这个DataSource默认就是HikariDataSource
//下面TransactionSynchronizationManager.getResource看看是如何获取ConnectionHolder的
//这里获取到的conHolder是null
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
//这里会把获取到的连接赋值给DataSourceTransactionObject
//第二个参数表示是不是一个新连接。我们这里只是获取到之前的(这里只是获取,不用管是不是null),并没有创建新的,所以这里是false
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
//TransactionSynchronizationManager的方法
@Nullable
public static Object getResource(Object key) {
//这里首先如果传入的key是包装过的,就去掉包装;我们这里没有包装,所以actualKey也就是key
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
//这里会通过ThreadLocal的方式来获取当前线程的连接,这里是一个map,key是datasource,所以这里是可以同时有多个datasouce的,并不会冲突。
//我们是第一次进来,map也是null,这里的value也就是null了。
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
//这里返回null
return value;
}
//AbstractPlatformTransactionManager的方法
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
//首先判断当前的事务传播属性,SYNCHRONIZATION_NEVER是不需要开启事务的
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
//这里是创建一个DefaultTransactionStatus对象
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//在这里,会判断是否开启了自动提交,如果开启了自动提交就改为手动提交,这样就需要在执行完所有SQL后,手动执行提交
//我们进去看看
doBegin(transaction, definition);
//这里会在在TransactionSynchronizationManager中设置属性,准备好事务开始
prepareSynchronization(status, definition);
return status;
}
//DataSourceTransactionManager的方法
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
//首先会判断ConnectionHolder是不是null,如果不是null,继续判断当前是不是已经在事务中
//我们这里ConnectionHolder是null,会走进if分支中
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//这里就是直接通过DataSource获取数据库的一个连接Connection
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//这里会将Connection包装成ConnectionHolder,set到DataSourceTransactionObject上
//同时这里是新创建的ConnectionHolder,第二个参数就是true
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
//这里设置开启事务
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
//这里是获取数据库的隔离级别
//如果我们事务属性中定义的隔离级别是默认的,那表示我们用数据当前的设置,这时我们不需要修改,这里回返回null
//如果我们事务属性中定义的不是默认的,那就先获取数据库当前的隔离级别,将数据库的隔离级别设置成我们定义的,同时返回数据库之前的隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
//上面的注释基本也说明了,这里就是开启手动提交
if (con.getAutoCommit()) {
//这里表示我们在执行完事务要还原成自动提交
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
//通过这句就是设置手动提交了
con.setAutoCommit(false);
}
//如果我们定义的是只读事务,在这里就会执行只读事务的设置
prepareTransactionalConnection(con, definition);
//设置事务开启标志
txObject.getConnectionHolder().setTransactionActive(true);
//获取超时时间,如果不是-1,说明需要设置超时时间。对于非法的超时时间,在前面已经做过判断了
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
//在这里判断如何是新创建的ConnectionHolder,通过ThreadLocal的方式和DataSource进行绑定,后续如果在同一个事务(只有在同一个DataSource、同一个connection中才是同一个事务)中,就可以直接获取到
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
2. 业务代码的调用
1. MethodProxy对象的创建
在上面的源码分析中,我们看到最后是通过调用invokeJoinpoint方法,在这个里面最终调用到this.methodProxy.invoke(this.target, this.arguments)的,那我们看看这里的methodProxy是怎么创建的
它是在我们的代理的CGLIB$STATICHOOK9()方法中创建的,相关的代码就是下面的了
Class var0 = Class.forName("com.springboot.transaction.jdbc.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$3f0b65e9");
Class var1 = Class.forName("com.springboot.transaction.jdbc.service.impl.UserServiceImpl");
//这里看到它会调用MethodProxy的静态方法来创建的
//前面两个参数分别是原始类和代理类,第3个参数我们事务数据对应的方法的签名
// 第4个参数是原始类中对应的方法名,第5个参数是对应生成的代理类中的方法中
//我们现在进去看看它内部的调用过程
CGLIB$addUser$0$Proxy = MethodProxy.create(var1, var0, "()Z", "addUser", "CGLIB$addUser$0");
//MethodProxy中的方法
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
//前面几行都比较简单,都是创建对象,属性赋值。我们看看下面这行,它会创建一个createInfo对象,并赋值到methodproxy对应属性上
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
public CreateInfo(Class c1, Class c2) {
//这里c1=om.springboot.transaction.service.impl.UserServiceImpl.class
//c2=com.springboot.transaction.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$f0a9d143.class
//c2也就是我们新生成的代理类
this.c1 = c1;
this.c2 = c2;
//下面会通过ThreadLocal的方式获取AbstractClassGenerator,主要是为了获取下面3个属性,保证代理类和我们这里最终要生成的快速执行方法的类相关设置保持一致
//我们看看这个是怎么获取到的
AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
if (fromEnhancer != null) {
namingPolicy = fromEnhancer.getNamingPolicy();
strategy = fromEnhancer.getStrategy();
attemptLoad = fromEnhancer.getAttemptLoad();
}
}
首先上面的方法调用是在我们执行完创建完事务的代理类之后,加载完代理类后就会调用到上面的方法,我们的代理类中会有个静态方法,执行类加载后就会执行静态方法
static {
CGLIB$STATICHOOK9();
}
上面的 CGLIB$STATICHOOK9方法会通过反射的方法获取我们原始类的方法,也会生成各种methodProxy。
所以 AbstractClassGenerator.getCurrent()这里得到的AbstractClassGenerator其实也就是我们创建代理类时使用的Enhancer对象,可以通过查看方法调用堆栈来确认
查看上图可以看到当前调用CreateInfo的构造方法,是在AbstractClassGenerator的generate时调用的。而在AbstractClassGenerator的generate的开始部分会有下面几句
protected Class generate(ClassLoaderData data) {
Class gen;
Object save = CURRENT.get();
//就在这里把this赋值给了CURRENT,而这里的this就是当前创建代理类用的Enhancer
CURRENT.set(this);
......
}
下面我们来看看this.methodProxy.invoke(this.target, this.arguments)的的执行
MethodProxy主要就是用来执行在原始类和代理类中方法调用的。
主要对应两个方法:
- invokeSuper方法,调用代理类中的方法
- invoke方法,调用原始类的方法
//MethodProxy的方法
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
//在init方法中会判断fastClassInfo对象是否存在,不存在就会去生成对应对象。
//init中也会生成2个代理类,下面我们看看init方法
init();
FastClassInfo fci = fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
catch (IllegalArgumentException ex) {
if (fastClassInfo.i1 < 0)
throw new IllegalArgumentException("Protected method: " + sig1);
throw ex;
}
}
//MethodProxy的方法
private void init() {
/*
* Using a volatile invariant allows us to initialize the FastClass and
* method index pairs atomically.
*
* Double-checked locking is safe with volatile in Java 5. Before 1.5 this
* code could allow fastClassInfo to be instantiated more than once, which
* appears to be benign.
*/
if (fastClassInfo == null) {
synchronized (initLock) {
if (fastClassInfo == null) {
CreateInfo ci = createInfo;
//首先创建一个FastClassInfo对象
//FastClassInfo主要有4个属性,在下面分别会对这4个属性赋值
FastClassInfo fci = new FastClassInfo();
//这里的ci就是上面的createInfo,
//ci.c1是我们的原始类com.springboot.transaction.service.impl.UserServiceImpl.class
//下面下面分别调用helper方法,生成快速调用原始类和代理类方法的FastClass类
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
//这里会根据方法签名在FastClass中返回一个整数,最终在调用invoke方法是时,会传入这个整数,在FastClass内部,通过switch-case的方式快速调用对应的方法
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}helper方法也会动态的生成一个叫FastClass的类,这个类的作用主要是通过数字下标的方式可以快速的调用一些类的方法。主要是代理类的方法。
个人理解
比如我们的代理类中方法CGLIB$findAll$0,这个是动态生成的。
再比如虽然我们的方法是不是动态生成的,但是springboot框架也是不知道我们的代码的,也没法直接调我们的代码。
由于上面两种情况,需要平台去调我们的代码方法,所以需要通过FastClass去根据我们的方法动态生成调用
下面的两行代码分别会生成对应我们原始类和代理类的FastClass
//helper方法还是通过创建FastClass.Generator对象,调用它的create动态创建FastClass
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
3.FastClass.Generator生成的FastClass。由于结构是一致的,这里就只展现代理类CGLIB$findAll$0方法对应的FastClass
它主要对应两个方法
- getIndex根据传入的Signature或者方法名跟类名返回一个整数,代码在invoke中的索引
- invoke根据传入的整数,和对应原始类或者代理类的参数,去调用代理类或原始类的方法
动态生成类class文件会根据我们main方法中指定的
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,p);
位置在这里生成.class。反编译就能看到
3. 事务方法执行后处理
前面已经看到事务的执行是在TransactionInterceptor这个类中invokeWithinTransaction方法中执行的,那我们现在去看看我们的代码执行完后,springboot是如何执行事务提交及后续工作的
//TransactionAspectSupport中的方法
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
//前面说到会在这里执行我们@Transactional注解的方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
//如果抛出异常,就会走到这里,在这里面根据情况进行回滚等操作,之后继续向外抛出异常
//注意:这里捕获的是Throwable,这是所有异常的最顶层接口,所以肯定能捕获到
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//在这里将TransactionInfo之前的信息继续存放到对应ThreadLocal变量中
//注意:由于是存放到ThreadLocal变量中,所以也不会有并发问题
cleanupTransactionInfo(txInfo);
}
//如果事务执行失败,在前面就抛出异常了,所以走到下面代码的,肯定是执行成功了,后面就会需要去提交事务
//这里不会存在io.vavr.control.Try.class,也就不会走到这个分支了
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
//在这里会进行事务的提交,我们进到这里面去看看
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
......
}
//TransactionAspectSupport中的方法
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
//通过事务管理器去提交事务,我们继续看里面的代码
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
//AbstractPlatformTransactionManager中的方法
public final void commit(TransactionStatus status) throws TransactionException {
//首先判断当前事务是否已经完成,completed这个标志是事务完成后设置成true的,这里还是false
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
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;
}
//在这里执行事务的提交
processCommit(defStatus);
}
/**
* Process an actual commit.
* Rollback-only flags have already been checked and applied.
* @param status object representing the transaction
* @throws TransactionException in case of commit failure
*/
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
//下面是个空方法,留给子类去继承的
prepareForCommit(status);
//下面是做一些提交前的处理
//主要是获取通过TransactionSynchronizationManager.getSynchronizations()获取所有TransactionSynchronization
//TransactionSynchronization这个接口为第三方框架服务的,在我们事务提交的阶段实行一些框架内部相关的工作
//如果数据库使用mybatis框架的话,这里会获取到SqlSessionUtils$SqlSessionSynchronization,这个主要是mybatis使用的,对我们事务框架的逻辑没有太大关系
//如果我们数据库交互用的是原生的JdbcTemplate这里就是空的,所以triggerBeforeCommit,triggerBeforeCompletion都不会执行什么操作
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
//如果之前有保存点的话,这里就需要释放掉,因为我们后面会对整个操作进行提交
//我们没有设置保存点,不会走到这里
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
//我们本地是开启的一个新的事务,会走到这里面去
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
//对于SmartTransactionObject,有可能会有回滚的情况,我们这里不是SmartTransactionObject,这里的返回值就是false
unexpectedRollback = status.isGlobalRollbackOnly();
//在这里执行真正的提交,比较简单就不进去看了。就是通过调用底层jdbc的connection的commit方法手动提交事务
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException | Error ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
//这本次也是空操作
triggerAfterCommit(status);
}
finally {
//这本次也是空操作
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
//在这里会进行一些清理工作,之前threadlocal变量设置的资源修改回原来的,把重新还原成自动提交,connection还给连接池等等
cleanupAfterCompletion(status);
}
}
4. 业务代码在事务和非事务中的区别
上面说的都是springboot对于事务的处理,对于我们业务代码来说,是否使用事务,内部处理也是有一些区别的。
- 主要是区别:
- 如果是在事务中执行,我们获取的真正执行数据库连接connection是之前在事务创建时获取的那个,也就是关闭自动提交,改为手动提交的那个。
- 如果不是在事务中执行,就会直接数据库连接池中直接获取
我们看看这部分关键代码
我们执行数据库操作获取数据库连接connection都是通过这句代码的
=DataSourceUtils.getConnection(obtainDataSource());
所以我们只需要看看这句代码的内部逻辑就会明白
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//如果之前开启了事务,这里的ConnectionHolder默认就不会为空,下面这个if条件也是成立的,就会从这里获取到数据库的连接
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource");
//如果没在事务中,或者从事务中获取到的是null,就会数据库连接池中新获取一个
Connection con = fetchConnection(dataSource);
//在这里会判断如果当前已经在事务中,能走到这个if分支中,说明当前事务中没有connection,那么就会把本次获取的connection方式到事务管理器中,在同一个事务中,执行多次数据库操作,最多只有一次会走到这里,之后的数据库操作都会从上面的if分支中返回
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
catch (RuntimeException ex) {
// Unexpected exception from external delegation call -> close Connection and rethrow.
releaseConnection(con, dataSource);
throw ex;
}
} return con;
}
5. 总结
从上面的流程可以看到其实事务大致流程如下:
通过动态生成代理类的方式,调用拦截器org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor的intercept方法。
在intercept方法中,获取对应的拦截器列表,调用invoke方法处理。我们这里只会有一个org.springframework.transaction.interceptor.TransactionInterceptor。继而调用到它的invokeWithinTransaction方法
在TransactionInterceptor的invokeWithinTransaction方法中会执行一系列事务的准备工作
获取事务属性信息、事务管理器
创建事务资源对象,获取数据库连接connection与当前事务进行绑定,根据当前事务定义的属性,设置隔离级别,传播行为,关闭自动提交,设置为手动提交。
设置当前事务为开启状态,将当前事务资源与当前线程进行绑定
通过invocation.proceedWithInvocation()调用,最终通过methodproxy.invoke最终调用到我们的业务代码
根据我们业务代码执行是否抛出异常来进行后续处理
- 如果抛出异常,认为当前事务执行失败,执行回滚等等回退操作,最终将异常继续向外抛
- 如果没有抛出异常,认为当前事务执行成功,执行事务的提交,同时清理资源。将事务隔离级别,传播行为等等还原成之前的状态。
最终将结果返回给调用者
整个事务处理大致的流程就是上面这些了 。
同时对比Springboot中注解@Configuration源码分析,你也会发现,springboot中动态生成类,基本都是一样的。都是套的同一个模版。
springboot 事务执行全流程分析的更多相关文章
- Kafka处理请求的全流程分析
大家好,我是 yes. 这是我的第三篇Kafka源码分析文章,前两篇讲了日志段的读写和二分算法在kafka索引上的应用 今天来讲讲 Kafka Broker端处理请求的全流程,剖析下底层的网络通信是如 ...
- Kafka控制器事件处理全流程分析
前言 大家好,我是 yes. 这是Kafka源码分析第四篇文章,今天来说说 Kafka控制器,即 Kafka Controller. 源码类的文章在手机上看其实效果很差,这篇文章我分为两部分,第一部分 ...
- 18张图,详解SpringBoot解析yml全流程
原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 前几天的时候,项目里有一个需求,需要一个开关控制代码中是否执行一段逻辑,于是理所当然的在yml文件中配置了一个属性作为开关,再配合nacos就可 ...
- SQL SERVER 事务执行情况跟踪分析
[sql] view plain copy ---查看现在所有的事务 select '正在运行事务的会话的 ID'=session_id, --session_id与transaction_id的对应 ...
- 监控视频采集与Web直播开发全流程分析
内容概要: 摄像头 => FFmpeg => Nginx服务器 => 浏览器 从摄像头拉取rtsp流 转码成rtmp流向推流服务器写入 利用html5播放 1.开发流程 1.1 通过 ...
- SpringBoot启动流程分析(六):IoC容器依赖注入
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(四):IoC容器的初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- springboot 事务创建流程源码分析
springboot 事务创建流程源码分析 目录 springboot 事务创建流程源码分析 1. 自动加载配置 2. InfrastructureAdvisorAutoProxyCreator类 3 ...
- 全球首个全流程跨平台界面开发套件,PowerUI分析
一. 首个全流程跨平台界面开发套件,PowerUI正式发布 UIPower在DirectUI的基础上,自主研发全球首个全流程跨平台界面开发套件PowerUI(PUI)正式发布,PowerU ...
随机推荐
- Appium自动化(16) - 使用手机浏览器进行自动化测试
如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 前言 前面我都讲的都是针对 app ...
- key存活时间和五个类型通用的一些指令操作
一.设置key的存活时间 1.失效后 ttl 是 -2,get返回 null : 2.不设置存活时候 ttl 返回是 -1: 二.查找指令和删除指令.重命名key.查看key类型 1.模糊查询 2.严 ...
- 博客主题——element v2
主题预览 主题下载 gshang.element-v2.rar
- ubuntu中如何切换普通用户、root用户
1.打开Ubuntu,输入命令:su root,回车提示输入密码,输入密码后提示:认证失败. 2.给root用户设置密码: 命令:sudo passwd root 输入密码,并确认密码. 3.重新输入 ...
- filter_var() 验证邮箱、ip、url的格式 php
验证邮箱格式的正确与否:你的第一解决方案是什么呢? 不管你们怎么思考的:反正我首先想到的就是字符串查找看是否有@符号: 但是对于结尾的.com或者.net 亦或者.cn等等越来越多的域名验证感觉棘手: ...
- P3307-[SDOI2013]项链【Burnside引理,莫比乌斯反演,特征方程】
正题 题目链接:https://www.luogu.com.cn/problem/P3307 题目大意 \(n\)个珠子的一个环形项链,每个珠子有三个\(1\sim k\)的整数. 两个珠子不同当且仅 ...
- CF1375F-Integer Game【交互】
正题 题目链接:https://www.luogu.com.cn/problem/CF1375F 题目大意 给出\(a,b,c\).先手每次指定一个数\(k\),然后后手指定一个数字加上\(k\),若 ...
- Appium+Python自动化环境搭建-1
前言 appium可以说是做app最火的一个自动化框架,它的主要优势是支持android和ios,另外脚本语言也是支持java和Python. 小编擅长Python,所以接下来的教程是appium+p ...
- 关于Windows操作系统重定向
在用C++做一个文件遍历的时候发现,当我遍历C:\Windows\system32文件夹时,获取到的文件数目和实际总是对不上.在通过他人帮助后了解到了重定向这个概念,我百度了一下,下面为粘贴内容. S ...
- 树莓派使用python+继电器控制220V灯泡
需要的材料 1.继电器:继电器是一种电控制器件,它实际上是用小电流去控制大电流运作的一种"自动开关",我们这里用它来控制电灯.控制了继电器就等于控制了电灯. 我购买的是某宝上3块钱 ...