SqlSessionTemplate是如何保證MyBatis中SqlSession的線程安全的?
周六的時候發了一篇面試題的整理《2018年最全Java面試通關秘籍匯總集!》,又在底部留言中補充了一道關於SqlSession線程安全性的問題,今天就帶大家初步探討一下這個問題,後期也會不斷從該面試題匯總集中抽出一部分試題和大家一起交流探討!還請小夥伴持續關注!
一、DefaultSqlSession的線程不安全性
在MyBatis架構中SqlSession是提供給外層調用的頂層介面,實現類有:DefaultSqlSession、SqlSessionManager以及mybatis-spring提供的實現SqlSessionTemplate。默認的實現類為DefaultSqlSession如。類圖結構如下所示:
對於MyBatis提供的原生實現類來說,用的最多的就是DefaultSqlSession,但我們知道DefaultSqlSession這個類不是線程安全的!如下:
二、SqlSessionTemplate是如何使用DefaultSqlSession的
而在我們開發的時候肯定會用到Spring,也會用到mybatis-spring框架,在使用MyBatis與Spring集成的時候我們會用到了SqlSessionTemplate這個類,例如下邊的配置,注入一個單例的SqlSessionTemplate對象:
SqlSessionTemplate的源代碼注釋如下:
通過源碼我們何以看到 SqlSessionTemplate實現了SqlSession介面,也就是說我們可以使用SqlSessionTemplate來代理以往的DefaultSqlSession完成對資料庫的操作,但是DefaultSqlSession這個類不是線程安全的,所以DefaultSqlSession這個類不可以被設置成單例模式的。
如果是常規開發模式的話,我們每次在使用DefaultSqlSession的時候都從SqlSessionFactory當中獲取一個就可以了。但是與Spring集成以後,Spring提供了一個全局唯一的SqlSessionTemplate對象來完成DefaultSqlSession的功能,問題就是:無論是多個Dao使用一個SqlSessionTemplate,還是一個Dao使用一個SqlSessionTemplate,SqlSessionTemplate都是對應一個sqlSession對象,當多個web線程調用同一個Dao時,它們使用的是同一個SqlSessionTemplate,也就是同一個SqlSession,那麼它是如何確保線程安全的呢?讓我們一起來分析一下:
三、SqlSessionTemplate是如何保證DefaultSqlSession線程安全的
(1)首先,通過如下代碼創建代理類,表示創建SqlSessionFactory的代理類的實例,該代理類實現SqlSession介面,定義了方法攔截器,如果調用代理類實例中實現SqlSession介面定義的方法,該調用則被導向SqlSessionInterceptor的invoke方法(代理對象的InvocationHandler就是SqlSessionInterceptor,如果把它命名為SqlSessionInvocationHandler則更好理解!)
核心代碼就在 SqlSessionInterceptor的invoke方法當中。
在上面的invoke方法當中使用了兩個工具方法分別是:
那麼這兩個方法又是如何與Spring的事物進行關聯的呢?
1、getSqlSession方法如下:
2、closeSqlSession方法如下:
大致的分析到此為止,可能有些許不夠順暢,不過:紙上得來終覺淺,絕知此事要躬行!還希望小夥伴打開自己的編譯器,找到此處的代碼,認真走一遍流程!
其實通過上面的代碼我們可以看出Mybatis在很多地方都用到了代理模式,代理模式可以說是一種經典模式,其實不緊緊在這個地方用到了代理模式,Spring的事物、AOP、Mybatis資料庫連接池技術、MyBatis的核心原理(如何在只有介面沒有實現類的情況下完成資料庫的操作!)等技術都使用了代理技術。
四、SqlSessionManager又是什麼鬼?
上述說了SqlSession的實現還有一個SqlSessionManager,那麼SqlSessionManager到底是什麼個東西哪?且看定義如下:
你可能會發現SqlSessionManager的構造方法竟然是private的,那我們怎麼創建這個對象哪?其實SqlSessionManager創建對象是通過newInstance的方法創建對象的,但需要注意的是他雖然有私有的構造方法,並且提供給我們了一個公有的newInstance方法,但它並不是一個單例模式!
newInstance有很多重載的方法,如下所示:
SqlSessionManager的openSession方法及其重載的方法是直接通過調用其中底層封裝的SqlSessionFactory對象的openSession方法來創建SqlSession對象的,重載方法如下:
SqlSessionManager中實現了SqlSession介面中的方法,例如:select、update等,都是直接調用sqlSessionProxy代理對象中相應的方法。在創建該代理對像的時候使用的InvocationHandler對象是SqlSessionInterceptor,他是定義在SqlSessionManager的一個內部類,其定義如下:
五、總結
綜上所述,我們應該大致了解了DefaultSqlSession和SqlSessionManager之間的區別:
1、DefaultSqlSession的內部沒有提供像SqlSessionManager一樣通過ThreadLocal的方式來保證線程的安全性;
2、SqlSessionManager是通過localSqlSession這個ThreadLocal變數,記錄與當前線程綁定的SqlSession對象,供當前線程循環使用,從而避免在同一個線程多次創建SqlSession對象造成的性能損耗;
3、DefaultSqlSession不是線程安全的,我們在進行原生開發的時候,需要每次為一個操作都創建一個SqlSession對象,其性能可想而知;
六、擴展面試題
就在上一次發了一篇面試總結之後,很多小夥伴就留言為什麼沒有參考答案,我在這裡給大家說,參考答案也只可能對那一個面試題有效果,如果面試官稍微擴展一下,你就可能不知所措,因此問題的本質還是需要我們去認證的研究其底層的具體實現細節,以不變應萬變,這裡擴展了兩個面試題,供大家學習交流:
1、為什麼mybatis-spring框架中不直接使用線程安全的SqlSessionManager(SqlSessionFactory它是線程安全的)而是使用DefaultSqlSession這個線程不安全的類,並通過動態代理的方式來保證DefaultSqlSession操作的線程安全性哪?
2、DefaultSqlSession中是如何通過Executor來表現策略模式的或者DefaultSqlSession如何使用策略模式模式的?
TAG:Java後端技術 |