Spring中實現多數據源事務管理


文章轉自  https://www.2cto.com/kf/201507/424229.html

前言

由於項目中引入了多個數據源,並且需要對多個數據源進行寫操作,那么多數據源的事務管理自然成了不可避免的問題,這也讓我對@Transactional注解有了進一步的理解(但實際上也並不是非常深入)

然而這是一個演進的過程,剛開始項目中並沒有使用@Transactional指定具體的TransactionManager,所以新增一個數據源后,對原有的事務產生了影響了,這也是偶爾在一次測試報錯而結果沒有回滾之后才發現的,遂對於@Transactional注解的一些參數項進行了了解。

研究

由於容器中存在兩個TransactionManager,那么被@Transactional注解的方法到底使用了哪個TransactionManager來進行事務管理,抑或是同時使用了兩個TransactionManager來進行事務管理都是我們需要搞清楚的問題。
首先我們先看看@Transactional注解上有沒有提供配置項來指定TransactionManager,果不其然,發現value屬性就是用來指定具體TransactionManager的,通過id或者name來指定唯一一個TransactionManager,那么對於只需要一個事務管理的方法,問題就簡單多了:

?
1
2
3
4
<code class = "hljs" cs= "" >    @Transactional (value = database2TransactionManager)
     public void test(String a) {
         // business operation
     }</code>

關於不指定TransactionManager時會使用哪一個TransactionManager,有興趣的童鞋可以參考另一篇文章,講的比較清晰:https://blog.sina.com.cn/s/blog_8f61307b0100ynfb.html

好了,回到我們研究的問題,那么對於需要寫入多個數據源的業務方法該怎么辦呢?

進一步研究

看來@Transactional是沒有提供這種功能了,那么就自己寫了一個吧。我記得Spring中的事務管理分編程式事務和聲明式事務。我們平時使用的@Transactional就是聲明式事務,它的好處其實也就是靈活度更高、代碼的耦合性更低,最終的事務管理實現還是一樣的,只不過將具體邏輯都剝離到了切面中。所以我們可以手寫一個切面來寫一次“編程式事務”,當然在具體應用時,還是聲明式的。

Java中一般編程式事務的寫法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<code class = "hljs" java= "" > public class UserServiceImpl implements UserService {
     @Resource
     private TransactionManager txManager;
     @Resource
     private UserDao userDao;
     @Resource
     private AddressDao addressDao;
 
     public boolean saveUser(User user) {
         TransactionDefinition txDefinition = new TransactionDefinition();
         TransactionStatus txStatus = txManager.getTransaction(txDefinition);
         boolean result = false ;
         try {
             result = userDao.save(user);
             if (!result){
                 return false ;
             }
             result = addressDao.save(user.getId(), user.getAddress());
             txManager.commit(txStatus);
         } catch (Exception e) {
             result = false ;
             txManager.rollback(txStatus);
         }
         return result;
     }
}</code>

我們借用這個邏輯將事務管理相關提取到切面中,並在進入目標方法之前,讓多個TransactionManager都開啟事務,並在成功執行后一並提交或失敗后一並回滾,具體代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<code class = "hljs" java= "" > /**
  * @author Zhu
  * @date 2015-7-15
  * @version 0.0.1
  * @description
  */
public class MultiTransactionalAspect {
 
     private Logger logger = LoggerFactory.getLogger(getClass());
 
     public Object around(ProceedingJoinPoint pjp,
             MultiTransactional multiTransactional) throws Throwable {
 
         Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack = new Stack<datasourcetransactionmanager>();
         Stack<transactionstatus> transactionStatuStack = new Stack<transactionstatus>();
 
         try {
 
             if (!openTransaction(dataSourceTransactionManagerStack,
                     transactionStatuStack, multiTransactional)) {
                 return null ;
             }
 
             Object ret = pjp.proceed();
 
             commit(dataSourceTransactionManagerStack, transactionStatuStack);
 
             return ret;
         } catch (Throwable e) {
 
             rollback(dataSourceTransactionManagerStack, transactionStatuStack);
 
             logger.error(String.format(
                     MultiTransactionalAspect, method:%s-%s occors error:, pjp
                             .getTarget().getClass().getSimpleName(), pjp
                             .getSignature().getName()), e);
             throw e;
         }
     }
 
     /**
      * @author Zhu
      * @date 2015-7-25下午7:55:46
      * @description
      * @param dataSourceTransactionManagerStack
      * @param transactionStatuStack
      * @param values
      */
     private boolean openTransaction(
             Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack,
             Stack<transactionstatus> transactionStatuStack,
             MultiTransactional multiTransactional) {
 
         String[] transactionMangerNames = multiTransactional.values();
         if (ArrayUtils.isEmpty(multiTransactional.values())) {
             return false ;
         }
 
         for (String beanName : transactionMangerNames) {
             DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) ContextHolder
                     .getBean(beanName);
             TransactionStatus transactionStatus = dataSourceTransactionManager
                     .getTransaction( new DefaultTransactionDefinition());
             transactionStatuStack.push(transactionStatus);
             dataSourceTransactionManagerStack
                     .push(dataSourceTransactionManager);
         }
         return true ;
     }
 
     /**
      * @author Zhu
      * @date 2015-7-25下午7:56:39
      * @description
      * @param dataSourceTransactionManagerStack
      * @param transactionStatuStack
      */
     private void commit(
             Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack,
             Stack<transactionstatus> transactionStatuStack) {
         while (!dataSourceTransactionManagerStack.isEmpty()) {
             dataSourceTransactionManagerStack.pop().commit(
                     transactionStatuStack.pop());
         }
     }
 
     /**
      * @author Zhu
      * @date 2015-7-25下午7:56:42
      * @description
      * @param dataSourceTransactionManagerStack
      * @param transactionStatuStack
      */
     private void rollback(
             Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack,
             Stack<transactionstatus> transactionStatuStack) {
         while (!dataSourceTransactionManagerStack.isEmpty()) {
             dataSourceTransactionManagerStack.pop().rollback(
                     transactionStatuStack.pop());
         }
     }</transactionstatus></datasourcetransactionmanager></transactionstatus></datasourcetransactionmanager></transactionstatus></datasourcetransactionmanager></transactionstatus></transactionstatus></datasourcetransactionmanager></datasourcetransactionmanager></code>

整體結構很清晰:
1. 首先根據指定的多個TransactionManager依次開啟事務,這個次序不影響,因為其實大家都是平等的。
2. 其次就是調用目標方法執行具體的業務邏輯
3. 若是成功返回則提交每個事務,若中途報錯,那么就回滾每個事務

其中為什么要用Stack來保存TransactionManagerTransactionStatus呢?那是因為Spring的事務處理是按照LIFO/stack behavior的方式進行的。如若順序有誤,則會報錯:

?
1
2
3
4
5
6
<code avrasm= "" class = "hljs" >java.lang.IllegalStateException: Cannot deactivate transaction synchronization - not active
         at org.springframework.transaction.support.TransactionSynchronizationManager.clearSynchronization(TransactionSynchronizationManager.java: 313 )
         at org.springframework.transaction.support.TransactionSynchronizationManager.clear(TransactionSynchronizationManager.java: 451 )
         at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java: 986 )
         at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java: 782 )
         at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactio</code>

題外話

剛開始碰到這個問題的時候,先想到的是分布式事務管理,也去看了JTA相關的文章,但是好像比較麻煩,而且都是一些老文章,於是想試試自己實現,最后也實現了。所以想知道JTA TransactionManager究竟有什么用呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM