排查了好久,終于解決,希望這次的排查過程對大家也有幫助,廢話少說,上源碼
開發(fā)環(huán)境
springboot 2.3.11
jdk8
gradle6.4
HikariDataSource
ps: 本環(huán)節(jié)使用雙數(shù)據(jù)源,在service層做切面攔截,切換具體的數(shù)據(jù)源
問題
在定義了具體的事務(wù)管理以后,想著不要手動切庫,因為事務(wù)管理那里已經(jīng)顯示的注入了具體的數(shù)據(jù)源,然后結(jié)果導(dǎo)致的是事務(wù)失效,發(fā)生異常不回滾。代碼如下
@Override
@Transactional(rollbackFor = Exception.class, transactionManager = DsConst.AGLOUD_TRANSACTION_MANAGER)
public void ex() {
jdbcTemplate.execute("insert into table_name1(name) values (1)");
if (1 == 1) throw new RuntimeException("出異常了?。。。?!");
jdbcTemplate.execute("insert into table_name2(name) values (1)");
}
排查過程
第一步,先排查源碼,spring攔截事務(wù)的攔截器是TransactionInterceptor類的invokeWithinTransaction方法,源碼如下
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = this.getTransactionAttributeSource();
//1:獲取事務(wù)屬性配置信息:通過 TransactionAttributeSource.getTransactionAttribute解析@Trasaction注解得到事務(wù)屬性配置信息
TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
//2:獲取事務(wù)管理器
TransactionManager tm = this.determineTransactionManager(txAttr);
//將事務(wù)管理器tx轉(zhuǎn)換為 PlatformTransactionManager
PlatformTransactionManager ptm = this.asPlatformTransactionManager(tm);
String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
//createTransactionIfNecessary內(nèi)部,這里就不說了,內(nèi)部主要就是使用spring事務(wù)硬
//編碼的方式開啟事務(wù),最終會返回一個TransactionInfo對象
TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
//業(yè)務(wù)方法返回值
Object retVal;
try {
//調(diào)用aop中的下一個攔截器,最終會調(diào)用到業(yè)務(wù)目標方法,獲取到目標方法的返回值
retVal = invocation.proceedWithInvocation();
} catch (Throwable var18) {
//3:異常情況下,如何走?可能只需提交,也可能只需回滾,這個取決于事務(wù)的配置
this.completeTransactionAfterThrowing(txInfo, var18);
throw var18;
} finally {
//清理事務(wù)信息
this.cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
//4:業(yè)務(wù)方法返回之后,只需事務(wù)提交操作
this.commitTransactionAfterReturning(txInfo);
return retVal;
}
當(dāng)我跟進去時候,發(fā)現(xiàn)事務(wù)并不是一個新的事務(wù)了,spring判斷回滾的代碼如下completeTransactionAfterThrowing,進去,最后執(zhí)行到這里
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
status.rollbackToHeldSavepoint();
}
//判斷是否是一個新的事務(wù),如果是就回滾
else if (status.isNewTransaction()) {
doRollback(status);
}
我很疑惑,為什么不是一個新的事務(wù),而且我也沒有整嵌套事務(wù)什么花里胡哨的東西,怎么就不是新的事務(wù)了。于是我另外起了一個項目對比debugger,好久才發(fā),具體如下

乍一看,我是不是眼花了?。?!為什么目標對象和代理對象都是代理對象呢。我xx,Cglib創(chuàng)建代理的時候,是可以代理類也再創(chuàng)建代理的嗎?本著求真務(wù)實的精神,用cglib的原生Api做了測試,發(fā)生會報錯,不能這樣子。代碼如下
package com.shiguiwu.springmybatis.spring.aop.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
/**
* @description: callbak
* 1. Cglib根據(jù)父類,Callback, Filter 及一些相關(guān)信息生成key
* 2. 然后根據(jù)key 生成對應(yīng)的子類的二進制表現(xiàn)形式
* 3. 使用ClassLoader裝載對應(yīng)的二進制,生成Class對象,并緩存
* 4. 最后實例化Class對象,并緩存
* @author: stone
* @date: Created by 2021/5/18 19:54
* @version: 1.0.0
* @pakeage: com.shiguiwu.springmybatis.spring.aop.cglib
*/
public class CglibCallbackObjTest {
interface Service1 {
void m1();
}
interface Service2 {
void m2();
}
public static class Service implements Service1, Service2 {
@Override
public void m1() {
System.out.println("m1");
}
@Override
public void m2() {
System.out.println("m2");
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//設(shè)置父類
enhancer.setSuperclass(Service.class);
//設(shè)置代理對象需要實現(xiàn)的接口
enhancer.setInterfaces(new Class[]{Service1.class, Service2.class});
//通過Callback來對被代理方法進行增強
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
long l = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
System.out.println(method.getName() + "耗時為:" + (System.nanoTime() - l));
return result;
});
Object proxy = enhancer.create();
//if (proxy instanceof Service) {
// ((Service) proxy).m1();
// ((Service) proxy).m2();
//}
System.out.println("父類" + proxy.getClass().getSuperclass());
System.out.println(proxy.getClass());
System.out.println("創(chuàng)建代理類實現(xiàn)的接口如下:");
for (Class<?> cs : proxy.getClass().getInterfaces()) {
System.out.println(cs);
}
Enhancer enhancer1 = new Enhancer();
enhancer1.setSuperclass(proxy.getClass());
enhancer1.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
long l = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
System.out.println(method.getName() + "耗時11為:" + (System.nanoTime() - l));
return result;
});
Object proxy1 = enhancer1.create();
System.out.println("父類" + proxy1.getClass().getSuperclass());
System.out.println(proxy1.getClass());
//if (proxy1 instanceof Service) {
// ((Service) proxy1).m1();
// ((Service) proxy1).m2();
//}
}
}
運行這段代碼會報錯
Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.ClassFormatError-->Duplicate method name&signature in class file com/shiguiwu/springmybatis/spring/aop/cglib/CglibCallbackObjTest$Service$$EnhancerByCGLIB$$c12e4df0$$EnhancerByCGLIB$$eed200b8
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.ap
但是,要知道,spring鬼的很,說不定他就是可以的,結(jié)果測試代碼,發(fā)現(xiàn),spring基于cglib創(chuàng)建的代理是可以,他會判斷當(dāng)前的目標對象是不是代理對象,如果是,則以目標對象的父類來創(chuàng)建代理對象,但是目標對象還是代理對象。代碼org.springframework.aop.framework.CglibAopProxy#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();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
這個時候,我們需要了解springbean的生命周期了,spring是什么時候,偷梁換柱的,把目標對象變成代理對象?,F(xiàn)有我知道的就是引入@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)注解,然后spring向容器中添加了一個AnnotationAwareAspectJAutoProxyCreator,這個類很重要,它是一個SmartInstantiationAwareBeanPostProcessor的子類,他就是springbean在初始化后調(diào)用的方法,然后判斷bean 需不需要生成代理對象。主要邏輯在抽象類AbstractAutoProxyCreator#postProcessAfterInitialization
@Override
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;
}
然后我把斷點打到這個位置,因為類比較多,為了不干擾我們調(diào)試,使用條件debugger,具體如下

最終發(fā)現(xiàn),有在創(chuàng)建代理對象的時候,上面的方法進了兩次。然后發(fā)現(xiàn)他們是不同的類進來的,原來我項目代碼中,創(chuàng)建代理用到了兩個類分別是AnnotationAwareAspectJAutoProxyCreator和DefaultAdvisorAutoProxyCreator。這個兩個類型具有相同的父類,也是BeanPostProcessor,也會攔截bean的創(chuàng)建過程。
但是我本地的項目主要AnnotationAwareAspectJAutoProxyCreator,于是為了模擬現(xiàn)場的環(huán)境,我手動注入了DefaultAdvisorAutoProxyCreator,最后發(fā)現(xiàn)本地是跟上次的情況一樣,創(chuàng)建了嵌套代理。
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
那是不是因為嵌套代理讓事務(wù)失效呢,最后的答案是:不是。本地照樣能回滾,原來我第一個打點斷的時候,只關(guān)注了是不是新事務(wù),但是spring aop是一個方法調(diào)用鏈,所以當(dāng)舊的事務(wù)通過調(diào)用棧出來的時候,這個事務(wù)就是一個新事務(wù),也執(zhí)行了回滾操作.
心累。。。
然后我在想是不是postgres沒有開啟事務(wù)支持呢,或者是數(shù)據(jù)庫需要手動設(shè)置事務(wù)支持。通過實驗我又排除了這個可能。代碼如下
@Autowired
private DataSource dataSource;
@Resource
private DataSource masterDataSource;
@Test
public void tx() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定義一個事務(wù)管理器
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(masterDataSource);
boolean a = true;
//清空數(shù)據(jù)
jdbcTemplate.update("truncate table table_name1");
System.out.println("PROPAGATION_REQUIRED start ==========================================================");
//2.定義一個事務(wù)屬性
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
//3.取一個事務(wù)狀態(tài),開啟事務(wù)了
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
//設(shè)置擴展點
//addSynchronization("ts-1", 2);
// //addSynchronization("ts-2", 1);
try {
//4.執(zhí)行業(yè)務(wù)
jdbcTemplate.execute("insert into table_name1(name) values (1)");
if (a) {
throw new RuntimeException("===");
}
//jdbcTemplate.update("insert into book(book_name) values (?)", "成是非");
jdbcTemplate.execute("insert into table_name1(name) values (100)");
//此時,在執(zhí)行一個方法事務(wù)
//other(jdbcTemplate, transactionManager);
//5.提交事務(wù)
System.out.println("PROPAGATION_REQUIRED 準備commit");
transactionManager.commit(transactionStatus);
System.out.println("PROPAGATION_REQUIRED commit完畢");
} catch (Exception e) {
e.printStackTrace();
//6.回滾事務(wù):platformTransactionManager.rollback
transactionManager.rollback(transactionStatus);
}
System.out.println("after==========================>:" + jdbcTemplate.queryForList("SELECT * from table_name1"));
}
經(jīng)過上面代碼,最終發(fā)現(xiàn),JdbcTemplate 的數(shù)據(jù)源和PlatformTransactionManager 管理器的數(shù)據(jù)源不是同一個,為什么不是同一個就失效呢?這里需要了解spring內(nèi)部是怎么控制事務(wù)的。
這里簡單說一下,源碼不是我們的重點:
spring在開始事務(wù)之前,會在事務(wù)管理器中拿數(shù)據(jù)源,通過該數(shù)據(jù)源獲取一個數(shù)據(jù)庫連接,同時將數(shù)據(jù)庫連接設(shè)置為手動提交,然后通過ThreadLocal綁定到當(dāng)前線程中,綁定的格式map類型datasource->ConnectionHolder。再來就是執(zhí)行業(yè)務(wù)了,jdbcTemplate中有數(shù)據(jù)源,這個時候,重點來了啊。根據(jù)jdbcTemplate的數(shù)據(jù)源,從本地線程中拿連接,此時由于數(shù)據(jù)源不一樣,根本拿不到連接,spring幫我們本次數(shù)據(jù)源中再創(chuàng)建一個連接,用此時的連接執(zhí)行sql,然后自動提交了。而我們保存線程本地的連接一直沒干活,所以事務(wù)失效!
再次排查
于是呢,我在事務(wù)方法哪里又手動進行切庫,這回總該數(shù)據(jù)源一致了吧。雖然說是動態(tài)數(shù)據(jù)源,但是具體返回的數(shù)據(jù)源應(yīng)該由我來設(shè)置的。

但是,事與愿違,還是不行,于是我在切庫的切面那里打了個斷點,代碼如下

然后發(fā)現(xiàn),代碼居然是先執(zhí)行了事務(wù)攔截器,在執(zhí)行的切面,炸了啊。怎么可能呢,查資料也是事務(wù)攔截器優(yōu)先級很低的,但是不管你怎么設(shè)置的切面順序,始終但是先執(zhí)行的事務(wù)攔截器。也就是說,事務(wù)管理里的數(shù)據(jù)源,不管你怎么切換,始終都是默認的數(shù)據(jù)源,這樣就實現(xiàn)不了其他庫的事務(wù)控制。最后回到最開始的問題,有一個事務(wù)攔截器是代理的代理,切面是目標對象的代理,所以不管你怎么設(shè)置,代理的代理方法始終是最先執(zhí)行的。
最后整個人都麻了,然后只能從為什么有兩個代理生成器的人手,最后通過尋找,在jar里面,有人顯示注入了DefaultAdvisorAutoProxyCreator,這個是由于項目遺留的bug,將那個源碼改掉就解決了。
回顧
本問題涉及的知識點比較多,事務(wù),代理,切面,spring bean的創(chuàng)建過程。本人水平有限,如有錯誤,請批評指正,多謝?。。?8888