問題描述: 寫主庫開事務的情況下會導致時不時的將更新/插入操作寫入到從庫上, 導致mysqlException update command denied
問題原因: jetty的工作隊列會重用處理線程, 導致threadLocal中的值被重用, 然而transaction注解在service層, 他會在DynamicDataSourceSwitch被設置之前直接去threadlocal拿數據, 本應拿到null, 但是拿到了之前線程的值
一般代碼調用鏈:
Repository@Annotation(AOP)-->DefaultSqlSession-->SimpleExecutor-->BaseExecutor.getConnection()-->SpringManagedTransaction.getConnection()--->連接為空-->AbstractRoutingDataSource.getConnection()-->拿到beforeAOP中注入的datasource的key, 所以每次都會動態切換數據源
事務代碼調用鏈:
service注解上@transactional-->
TransactionInterceptor.interpter()-->TransactionAspectSupport.createTransactionIfNecessary()-->AbstractPlatformTransactionManager.getTransaction()-->DataSourceTransactionManager.doBegin()-->AbstractRoutingDataSource.determineTargetDataSource()[lookupKey==null去拿默認的Datasource, 不為空則使用獲取到的連接]-->DataSourceTransactionManager.setTransactional()[將連接設置到TransactionUtils的threadLocal中]--->Repository@Annotation-->執行一般調用鏈, 問題在於SpringManagedTransaction.getConnection()-->openConnection()-->DataSourceUtils.getConnection()-->TransactionSynchronizationManager.getResource(dataSource)不為空[從TransactionUtils的threadLocal中獲取數據源], 所以不會再去調用DynamicDataSource去獲取數據源
有時間再補一個Sequence圖
問題解決, DataSourceAdvice AfterReturn需要刪除threadLocal中的數據源key
public class DataSourceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice { private static final Logger LOG = LoggerFactory.getLogger(DataSourceAdvice.class); @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { DataSourceSwitcher.clearDataSource(); } }