關於Spring事務的原理,以及在事務內開啟線程,連接池耗盡問題.


主要以結果為導向解釋Spring 事務原理,連接池的消耗,以及事務內開啟事務線程要注意的問題.

Spring 事務原理這里不多說,網上一搜一大堆,也就是基於AOP配合ThreadLocal實現.

這里強調一下Spring Aop 以及Spring 注解式注入在非Spring容器管理的類中是無效的.

因為Spring Aop是在運行時實現字節碼增強,字節碼增強有多種實現方法,請自行了解,原生AspectJ是編譯時織入,但是需要特定的編譯器.語法並沒有Spring Aop好理解.

 

先看下Spring 事務傳播行為類型

 

事務傳播行為類型

說明

PROPAGATION_REQUIRED

如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是 最常見的選擇。

PROPAGATION_SUPPORTS

支持當前事務,如果當前沒有事務,就以非事務方式執行。

PROPAGATION_MANDATORY

使用當前的事務,如果當前沒有事務,就拋出異常。

PROPAGATION_REQUIRES_NEW

新建事務,如果當前存在事務,把當前事務掛起。

PROPAGATION_NOT_SUPPORTED

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

PROPAGATION_NEVER

以非事務方式執行,如果當前存在事務,則拋出異常。

PROPAGATION_NESTED

如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與 PROPAGATION_REQUIRED類似的操作。

 

打開日記debug模式,留意控制台輸出

以下測試為了可讀性以及更容易理解全是基於Spring注解式事務,而沒有配置聲明式事務.

 

測試1:

可以看見只創建了一個SqlSession以及一個事務,在方法內所有操作都使用同一個連接,同一個事務

@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
    @Transactional(propagation = Propagation.REQUIRED)
    public void testThreadTx(){
//此方法沒有事務(當前方法是 Propagation.REQUIRED)
            Quotation quotation = quotationService.findEntityById(new String("1"));
//此方法沒有事務(當前方法是 Propagation.REQUIRED)
            quotationService.updateEntity(quotation);
    }

//查看控制台輸出(紅字關鍵部分,第三個查詢是更新方法內部需要先查詢一次再更新,可以無視)

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
ansaction(line/:54) -JDBC Connection [1068277098(com.mysql.jdbc.JDBC4Connection@5d92bace)] will be managed by Spring
otationMapper.findEntityById(line/:54) -==>  Preparing: SELECT * FROM table WHERE id = 1 
otationMapper.findEntityById(line/:54) -==> Parameters: 
otationMapper.findEntityById(line/:54) -<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4] from current transaction
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4] from current transaction
otationMapper.updateEntity(line/:54) -==>  Preparing: update ….. where id = 1 
otationMapper.updateEntity(line/:54) -==> Parameters: 
otationMapper.updateEntity(line/:54) -<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
.impl.UserOperationLogServiceImpl(line/:41) -請求所用時間:207
.impl.UserOperationLogServiceImpl(line/:42) -請求結束*******************************************************************************
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]

 

測試2:不使用事務

可以看出在非事務操作數據庫,會使用多個連接,非常不環保,這里給稍微多線程插入埋下一個陷阱

@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
//    @Transactional(propagation = Propagation.REQUIRED)
    public void testThreadTx(){
            Quotation quotation = quotationService.findEntityById(new String("1"));
            quotationService.updateEntity(quotation);
    }
//查看控制台輸出(紅字關鍵部分,第三個查詢是更新方法內部需要先查詢一次再更新,可以無視)
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f7b94f] was not registered for synchronization because synchronization 
ansaction(line/:54) -JDBC Connection [352410768(com.mysql.jdbc.JDBC4Connection@c63fcb6)] will not be managed by Spring
otationMapper.findEntityById(line/:54) -==>  Preparing: SELECT * FROM table WHERE id = 1 
otationMapper.findEntityById(line/:54) -==> Parameters: 
otationMapper.findEntityById(line/:54) -<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f7b94f]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a41785a] was not registered for synchronization because synchronization 
ansaction(line/:54) -JDBC Connection [1615108970(com.mysql.jdbc.JDBC4Connection@38377d86)] will not be managed by Spring
otationMapper.findEntityById(line/:54) -==>  Preparing: SELECT * FROM table WHERE id = 1 
otationMapper.findEntityById(line/:54) -==> Parameters: 
otationMapper.findEntityById(line/:54) -<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a41785a]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@181e5a22] was not registered for synchronization because synchronization 
ansaction(line/:54) -JDBC Connection [2096339748(com.mysql.jdbc.JDBC4Connection@5d4e9892)] will not be managed by Spring
otationMapper.updateEntity(line/:54) -==>  Preparing: update …. where id = 1 
otationMapper.updateEntity(line/:54) -==> Parameters: 
otationMapper.updateEntity(line/:54) -<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@181e5a22]
.impl.UserOperationLogServiceImpl(line/:41) -請求所用時間:614
.impl.UserOperationLogServiceImpl(line/:42) -請求結束*******************************************************************************

測試3:

@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
    @Transactional(propagation = Propagation.REQUIRED)
    public void testThreadTx(){
            final ExecutorService executorService = Executors.newFixedThreadPool(3);
            Quotation quotation = quotationService.findEntityById(new String("1"));
            quotationService.updateEntity(quotation);
            List<Future<Integer>> futures = new ArrayList<Future<Integer>>(3);
            for(int i=0;i<3;i++){
                    Callable<Integer> task = new Callable<Integer>() {
                        @Override
                        public Integer call() throws Exception {
                            Quotation quotation = quotationService.findEntityById(new String("1"));
                            quotationService.updateEntity(quotation);
                            return null;
                        }
                    };
                    futures.add(executorService.submit(task));
            }
            executorService.shutdown();
    }

//查看控制台輸出(紅字關鍵部分,第三個查詢是更新方法內部需要先查詢一次再更新,可以無視)

為了節篇幅,這里不貼出控制台數據

大概就是輸出了10個Creating a new SqlSession(大概有些同學使用了多線程,把線程池耗完了也沒弄明白原因)

外層方法啟動一個,內部3個線程,每個線程3個.一共是使用了10個連接.

為什么?這涉及到ThreadLocal以及線程私有棧的概念.如果Spring 事務使用InhertableThreadLocal就可以把連接傳到子線程,但是為什么Spring不那么干呢?因為這樣毫無意義,如果把同一個連接傳到子線程,那就是SQL操作會串行執行,那何必還多線程呢?

有關於ThreadLocal,InhertableThreadLocal配合線程池的一些陷阱

請看我另一篇文章:

ThreadLoacl,InheritableThreadLocal,原理,以及配合線程池使用的一些坑

 

測試4:

既然使用同一個事務,不能實現並發操作,那么只能折中,在每一個線程開啟一個事務,減少創建更多的連接,執行完畢以后可以返回操作成功失敗結果,反饋給用戶

@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
//    @Transactional(propagation = Propagation.REQUIRED)
    public void testThreadTx(){
            ExecutorService executorService = Executors.newFixedThreadPool(3);
            List<Future<Integer>> futures = new ArrayList<Future<Integer>>(3);
            for(int i=0;i<3;i++){
                    Callable<Integer> task = new Callable<Integer>() {
                        @Override
                        public Integer call() throws Exception {
                            quotationService.doSomeThing();
                            return null;
                        }
                    };
                    futures.add(executorService.submit(task));
            }
            executorService.shutdown();
    }

    //封裝一下
    @Override
    @Transactional(propagation =Propagation.REQUIRED)
    public void doSomeThing(){
        Quotation quotation = this.findEntityById(new String("1"));
        this.updateEntity(quotation);
    }
//查看控制台輸出,只會創建3個連接,為節省篇幅,這里不貼出控制台所有數據
Creating a new SqlSession
Creating a new SqlSession
Creating a new SqlSession

最后小技巧PROPAGATION_NOT_SUPPORTED(僅僅為了讓Spring能獲取ThreadLocal的connection),如果不使用事務,但是同一個方法多個對數據庫操作,那么使用這個傳播行為可以減少消耗數據庫連接

    @RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void testThreadTx(){
        Quotation quotation = quotationService.findEntityById(new String("1"));
        quotation.setStatus(ClassDataManager.STATE_N);
        quotationService.updateEntity(quotation);
    }
//這樣只會創建一個SqlSession

 


免責聲明!

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



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