事務導致多數據源AbstractRoutingDataSource切換數據源失敗問題解決

原文鏈接:https://yq.aliyun.com/articles/250654

多數據源切換失敗問題解決


@Transactional註解(事務)會導致多數據源切換失敗!
在一個方法中,有多個SQL語句執行,我們習慣性的會加上@Transactional註解,但是!這隻侷限於這多個SQL語句(不論增刪改查),都只訪問一個庫!(大坑!)
例如:

@Transactional
public void test(){
   mysqlMapperOne.insert(....);
   
   //此處爲切換數據源的操作。。。。省略
   
   mysqlMapperTwo.insert(.....);
}

這個會導致mysqlMapperTwo的SQL語句被映射到第一個SQL語句的庫中。導致報錯,在第一個數據源中找不到第二個數據源的表。。。。這個問題我一直琢磨了大半天。。。
原因:
在org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin 這個類的源代碼中找到了答案:

 @Override
 2     protected void doBegin(Object transaction, TransactionDefinition definition) {
 3         DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
 4         Connection con = null;
 5 
 6         try {
 7             if (txObject.getConnectionHolder() == null ||
 8                     txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
 9                 Connection newCon = this.dataSource.getConnection();
10                 if (logger.isDebugEnabled()) {
11                     logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
12                 }
13                 txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
14             }
15 
16             txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
17             con = txObject.getConnectionHolder().getConnection();
18 
19             Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
20             txObject.setPreviousIsolationLevel(previousIsolationLevel);
21 
22             // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
23             // so we don't want to do it unnecessarily (for example if we've explicitly
24             // configured the connection pool to set it already).
25             if (con.getAutoCommit()) {
26                 txObject.setMustRestoreAutoCommit(true);
27                 if (logger.isDebugEnabled()) {
28                     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
29                 }
30                 con.setAutoCommit(false);
31             }
32 
33             prepareTransactionalConnection(con, definition);
34             txObject.getConnectionHolder().setTransactionActive(true);
35 
36             int timeout = determineTimeout(definition);
37             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
38                 txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
39             }
40 
41             // Bind the connection holder to the thread.
42             if (txObject.isNewConnectionHolder()) {
43                 TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
44             }
45         }
46 
47         catch (Throwable ex) {
48             if (txObject.isNewConnectionHolder()) {
49                 DataSourceUtils.releaseConnection(con, this.dataSource);
50                 txObject.setConnectionHolder(null, false);
51             }
52             throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
53         }
54     }

第7-16行,在開始一個事務前,如果當前上下文的連接對象爲空,獲取一個連接對象,然後保存起來,下次doBegin再調用時,就直接用這個連接了,根本不做任何切換(類似於緩存命中!),說白了,就是 “他” 發現你之前調用過連接,他就不管另一個了

在方法被調用前,已經和另一個數據庫發生了關係,所以他就直接從這個裏面拿了,所以第二個他就無視了。

解決辦法:把事務刪了!!或者,先切到主庫上來,這樣後面再調用有事務的方法時,就仍然保持在主庫的連接上。

原文鏈接:https://yq.aliyun.com/articles/250654
感謝作者:楊俊明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章