1. 程式人生 > >SqlSessionTemplate是如何保證的MyBatis中的SqlSession的執行緒安全的?

SqlSessionTemplate是如何保證的MyBatis中的SqlSession的執行緒安全的?

一,DefaultSqlSession的執行緒不安全性

在MyBatis的架構中的SqlSession是提供給外層呼叫的頂層介面,實現類有:DefaultSqlSession,SqlSessionManager以及MyBatis的彈簧提供的實現SqlSessionTemplate預設的實現類為DefaultSqlSession如類圖結構如下所示:。
這裡寫圖片描述 
對於MyBatis的提供的原生實現類來說,用的最多的就是DefaultSqlSession,但我們知道DefaultSqlSession這個類不是執行緒安全的如下!
這裡寫圖片描述

二,SqlSessionTemplate是如何使用DefaultSqlSession的

而在我們開發的時候肯定會用到春天,也會用到MyBatis的彈簧框架,在使用的MyBatis與彈簧整合的時候我們會用到了SqlSessionTemplate這個類,例如下邊的配置,注入一個單例的SqlSessionTemplate物件:

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg ref="sqlSessionFactory" />
</bean>
  • 1
  • 2
  • 3

SqlSessionTemplate的原始碼註釋如下:
這裡寫圖片描述 
這裡寫圖片描述

 
通過原始碼我們何以看到SqlSessionTemplate實現了SqlSession介面,也就是說我們可以使用SqlSessionTemplate來代理以前的DefaultSqlSession完成對資料庫的操作,但是是DefaultSqlSession這個類不是執行緒安全的,所以DefaultSqlSession這個類不可以被設定成單例模式的。

如果是常規開發模式的話,我們每次在使用DefaultSqlSession的時候都從SqlSessionFactory物件當中獲取一個就可以了但是與彈簧整合以後,彈簧提供了一個全域性唯一的SqlSessionTemplate物件來完成DefaultSqlSession的功能,問題就是:無論是多個道使用一個SqlSessionTemplate,還是一個道使用一個SqlSessionTemplate,SqlSessionTemplate都是對應一個SQLSESSION物件,當多個網路執行緒呼叫同一個島時,它們使用的是同一個SqlSessionTemplate,也就是同一個SqlSession的,那麼它?是如何確保執行緒安全的呢讓我們一起來分析一下:

三,SqlSessionTemplate是如何保證DefaultSqlSession執行緒安全的

(1)首先,通過如下程式碼建立代理類,表示建立的SqlSessionFactory的代理類的例項,該代理類實現的SqlSession介面,定義了方法攔截器,如果呼叫代理類例項中實現的SqlSession介面定義的方法,該呼叫則被導向SqlSessionInterceptor的invoke方法(代理物件的InvocationHandler就是SqlSessionInterceptor,如果把它命名為SqlSessionInvocationHandler則更好理解!)核心
這裡寫圖片描述 
程式碼就在SqlSessionInterceptor的invoke方法當中。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
        //獲取SqlSession(這個SqlSession才是真正使用的,它不是執行緒安全的)
        //這個方法可以根據Spring的事物上下文來獲取事物範圍內的sqlSession
        SqlSession sqlSession = getSqlSession(
                SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType,
                SqlSessionTemplate.this.exceptionTranslator);
        try {
            //呼叫從Spring的事物上下文獲取事物範圍內的sqlSession物件
            Object result = method.invoke(sqlSession, args);
            //然後判斷一下當前的sqlSession是否被Spring託管 如果未被Spring託管則自動commit
            if (!isSqlSessionTransactional(sqlSession, 
                    SqlSessionTemplate.this.sqlSessionFactory)) {
                // force commit even on non-dirty sessions because some databases require
                // a commit/rollback before calling close()
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            //如果出現異常則根據情況轉換後丟擲
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && 
                    unwrapped instanceof PersistenceException) {
                // release the connection to avoid a deadlock if the 
                // translator is no loaded. See issue #22
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.
                        translateExceptionIfPossible((PersistenceException) unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw unwrapped;
        } finally {
            if (sqlSession != null) {
                //關閉sqlSession,它會根據當前的sqlSession是否在Spring的事物上下文當中來執行具體的關閉動作
                //如果sqlSession被Spring管理 則呼叫holder.released();
                //使計數器-1,否則才真正的關閉sqlSession
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 三十
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

在上面的呼叫方法當中使用了兩個工具方法分別是:

1)SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)
(2)SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
  • 1
  • 2

那麼這兩個方法又是如何與春天的事物進行關聯的呢?

1,getSqlSession方法如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, 
        ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    //根據sqlSessionFactory從當前執行緒對應的資源map中獲取SqlSessionHolder,
    // 當sqlSessionFactory建立了sqlSession,
    //就會在事務管理器中新增一對對映:key為sqlSessionFactory,value為SqlSessionHolder,
    // 該類儲存sqlSession及執行方式
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.
            getResource(sessionFactory);

    //從SqlSessionHolder中提取SqlSession物件
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    }

    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Creating a new SqlSession");
    }
    //如果當前事物管理器中獲取不到SqlSessionHolder物件就重新建立一個
    session = sessionFactory.openSession(executorType);

    //將新建立的SqlSessionHolder物件註冊到TransactionSynchronizationManager中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 三十

2,closeSqlSession方法如下:

//刪除了部分日誌程式碼
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
        //其實下面就是判斷session是否被Spring事務管理,如果管理就會得到holder
        SqlSessionHolder holder = (SqlSessionHolder) 
        TransactionSynchronizationManager.getResource(sessionFactory);
        if ((holder != null) && (holder.getSqlSession() == session)) {
            //這裡釋放的作用,不是關閉,只是減少一下引用數,因為後面可能會被複用 
            holder.released();
        } else {
            //如果不是被spring管理,那麼就不會被Spring去關閉回收,就需要自己close 
            session.close();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

大致的分析到此為止,可能有些許不夠順暢,不過:紙上得來終覺淺,絕知此事要躬行還希望小夥伴開啟自己的編譯器,找到此處的程式碼,認真走一遍流程!

其實通過上面的程式碼我們可以看出的MyBatis在很多地方都用到了代理模式,代理模式可以說是一種經典模式,其實不緊緊在這個地方用到了代理模式,春天的事物,AOP,MyBatis的資料庫連線池技術,MyBatis的的核心原理(如何在只有介面沒有實現類的情況下完成資料庫的操作!)等技術都使用了代理技術。

四,SqlSessionManager又是什麼鬼?

上述說了一個SqlSession的實現還有一個SqlSessionManager,那麼SqlSessionManager到底是什麼個東西哪且看定義如下:? 
這裡寫圖片描述 
你可能會發現SqlSessionManager的構造方法竟然是私人的,那我們怎麼建立這個物件哪其實SqlSessionManager建立物件是通過的newInstance的方法建立物件的,但需要注意的是他雖然有私有的構造方法,並且提供給我們了一個公有的的newInstance方法,但它並不是一個單例模式!

newInstance有很多過載的方法,如下所示:
這裡寫圖片描述 
SqlSessionManager的openSession方法及其過載的方法是直接通過呼叫其中的底層封裝的SqlSessionFactory物件的openSession方法來建立SqlSession物件的,過載方法如下:
這裡寫圖片描述 
SqlSessionManager中實現了SqlSession的介面中的方法,例如:選擇,更新等,都是直接呼叫sqlSessionProxy代理物件中相應的方法在建立該代理對像的時候使用的InvocationHandler的物件是SqlSessionInterceptor,他是定義在SqlSessionManager的一個內部類,其定義如下:
這裡寫圖片描述

五,總結

綜上所述,我們應該大致瞭解了DefaultSqlSession和SqlSessionManager之間的區別:

1,DefaultSqlSession的內部沒有提供像SqlSessionManager一樣通過ThreadLocal的的方式來保證執行緒的安全性;

2,SqlSessionManager是通過localSqlSession這個ThreadLocal的變數,記錄與當前執行緒繫結的SqlSession的物件,供當前執行緒迴圈使用,從而避免在同一個執行緒多次建立的SqlSession物件造成的效能損耗;

3,DefaultSqlSession不是執行緒安全的,我們在進行原生開發的時候,需要每次為一個操作都建立一個SqlSession的物件,其效能可想而知;

六,擴充套件面試題

那麼問題來了:

1,為什麼MyBatis的彈簧框架中不直接使用執行緒安全的SqlSessionManager(SqlSessionFactory中它是執行緒安全的)而是使用DefaultSqlSession這個執行緒不安全的類,並通過動態代理的方式來保證DefaultSqlSession操作的執行緒安全性哪?

2,DefaultSqlSession中是如何通過執行器來表現策略模式的或者DefaultSqlSession如何使用策略模式模式的?


相關推薦

Qt實現執行安全的單例模式

之前專案中用到單例模式,用的是執行緒不安全的,這次專案用到了多執行緒,所以想到實現一個執行緒安全的單例模式。經過查詢資料,發現Qt本身有自己的執行緒安全單例模式實現方式。 Q_GLOBAL_STATIC巨集 使用方法:MonitorWindow.h #ifndef MONITORW

Java語言執行安全

執行緒安全定義:當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,也不需要考慮進行額外的同步,或者在呼叫方進行任何其他的寫作操作,呼叫這個物件的行為都可以獲得正確的結果,那這個物件時執行緒安全的。 將Java語言中的各種操作共享的資料分為以下五類: 不可

spring單例模式執行安全問題

@RequestMapping(value = "getPsdbData", method = RequestMethod.POST) public Map<String, Object> getPsdbData(String key,HttpServletRequest reques

保證List物件的執行安全例子

因為擔心resetGuestApiList時,多執行緒讀取guestApiList時可能會不安全。所以加鎖限制了。其他地方只對guestApiList進行只讀操作,這樣guestApiList應該是安全

單例模式的懶漢式及其執行安全問題

先看程式碼: package com.roocon.thread.t5; public class Singleton2 { private Singleton2(){ } private static Singleton2 instan

java執行安全與鎖優化

Java的執行緒是對映到作業系統的原生執行緒之上的,如果要阻塞或喚醒一條執行緒,都需要作業系統來幫忙完成,這就需要作業系統來幫忙完成,需要從使用者態轉換到核心態中,狀態轉換需要耗費很多的處理器時間。如果是非常簡單的程式碼同步塊,狀態轉換消耗的時間可能比使用者程式碼執行的時間還要長。 因此可以說,syn

C++ 標準庫執行安全

執行緒安全規則應用到標準 C++ 庫中的所有類,這也包括 shared_ptr,如下所述。 有時提供更強的保證(例如,如下所述的標準 iostream 物件和專門用於多執行緒的型別,如 中的型別)。 從多個執行緒讀取某個物件時,該物件是執行緒安全的。 例

JAVA執行安全與非執行安全

ArrayList和Vector有什麼區別?HashMap和HashTable有什麼區別?StringBuilder和StringBuffer有什麼區別?這些都是Java面試中常見的基礎問題。面對這樣的問題,回答是:ArrayList是非執行緒安全的,Vector是執行緒

java集合執行安全問題

1.Collection是無序的,允許元素重複  List集合是有序的,允許元素重複  Set集合   HashSet、AbstractSet集合是無序的,TreeSet集合是有序的,不允許元素重複,

Java執行安全

執行緒安全問題之所以存在,本質原因是: 當多個執行緒訪問同一個資料的時候,可能引起衝突。而且這些執行緒中至少有一個執行緒會改寫這個資料時才會出現衝突,如果所有執行緒都只讀不改寫,則不會衝突。由於這個資料不只被一個執行緒訪問,我們稱這個資料為共享資料。 類中一

聊聊Servlet、Struts1、Struts2以及SpringMvc執行安全

前言很多初學者,甚至是工作1-3年的小夥伴們都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是單例,哪些是多例,哪些是執行緒安全?在談這個話題之前,我們先了解一下Java中相關的變數型別以及記憶體模型JMM。變數型別類變數:獨立於方法之外的變數,用 static 修飾。

Linux/Unix程式設計執行安全問題

在目前的電腦科學中,執行緒是作業系統排程的最小單元,程序是資源分配的最小單元。在大多數作業系統中,一個程序可以同時派生出多個執行緒。這些執行緒獨立執行,共享程序的資源。在單處理器系統中,多執行緒通過分時複用技術來技術,處理器在不同的執行緒間切換,從而更高效地利用系統 CPU

JAVA執行安全與非執行安全理解

執行緒安全性不是一個非真即假的命題。 Vector 的方法都是同步的,並且 Vector 明確地設計為在多執行緒環境中工作。但是它的執行緒安全性是有限制的,即在某些方法之間有狀態依賴(類似地,如果在迭代過程中 Vector 被其他執行緒修改,那麼由 Vector.iterator() 返回的 itera

Cocos2d-X多執行(3) cocos2dx執行安全

在使用多執行緒時,總會遇到執行緒安全的問題。cocos2dx 3.0系列中新加入了一個專門處理執行緒安全的函式performFunctionInCocosThread(),他是Scheduler類的一個成員函式: void Scheduler::performFunction

在Java構建執行安全的Set

我們常用的HashSet、TreeSet都是執行緒不安全的,在JDK1.8中ConcurrentHashMap類中新增了兩個方法,

SqlSessionTemplate是如何保證MyBatisSqlSession執行安全的?

一,DefaultSqlSession的執行緒不安全性 在MyBatis的架構中的SqlSession是提供給外層呼叫的頂層介面,實現類有:DefaultSqlSession,SqlSessionManager以及MyBatis的彈簧提供的實現SqlSession

iOS保證執行安全的幾種方式與效能對比

一、前言 前段時間看了幾個開源專案,發現他們保持執行緒同步的方式各不相同,有@synchronized、NSLock、dispatch_semaphore、NSCondition、pthread_mutex、OSSpinLock。後來網上查了一下,發現他們的實現機制各不相同,效能也各不一

Java多執行有哪幾種實現方式? Java的類如何保證執行安全? 請說明ThreadLocal的用法和適用場景(面試題)

Java多執行緒有哪幾種實現方式? Java中的類如何保證執行緒安全? 請說明ThreadLocal的用法和適用場景 Java多執行緒有三種實現方式: (1)繼承Thread類,重寫run函式 (2)實現Runnable介面,重寫run函式 開啟執行緒:Thread t

執行如何去保證執行安全

一、前言   前段時間看了幾個開源專案,發現他們保持執行緒同步的方式各不相同,有@synchronized、NSLock、dispatch_semaphore、NSCondition、pthread_mutex、OSSpinLock。後來網上查了一下,發現他們的實現機制各不相同,效能也各不一樣。

在 Java 的多執行,如何去判斷給定的一個類是否是執行安全的(另外:synchronized 同步是否就一定能保證該類是執行安全的。)

同步程式碼塊和同步方法的區別:同步程式碼塊可以傳入任意物件,同步方法中 如果多個執行緒檢查的都是一個新的物件,不同的同步鎖對不同的執行緒不具有排他性,不能實現執行緒同步的效果,這時候執行緒同步就失效了。   兩者的區別主要體現在同步鎖上面。對於例項的同步方法,因為只能使用