多數據源切換失敗問題解決
@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
感謝作者:楊俊明