摘要:從源碼的角度分析源碼分析從哪一步作為入口呢如果是看過我之前寫的那幾篇關(guān)于的源碼分析,我相信你不會(huì)在源碼前磨磨蹭蹭,遲遲找不到入口。
微信公眾號(hào)「后端進(jìn)階」,專注后端技術(shù)分享:Java、Golang、WEB框架、分布式中間件、服務(wù)治理等等。
老司機(jī)傾囊相授,帶你一路進(jìn)階,來不及解釋了快上車!
坐在我旁邊的鐘同學(xué)聽說我精通Mybatis源碼(我就想不通,是誰透漏了風(fēng)聲),就順帶問了我一個(gè)問題:在同一個(gè)方法中,Mybatis多次請(qǐng)求數(shù)據(jù)庫,是否要?jiǎng)?chuàng)建多個(gè)SqlSession會(huì)話?
可能最近擼多了,當(dāng)時(shí)腦子里一片模糊,眼神迷離,雖然我當(dāng)時(shí)回答他:如果多個(gè)請(qǐng)求同一個(gè)事務(wù)中,那么多個(gè)請(qǐng)求都在共用一個(gè)SqlSession,反之每個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)SqlSession。這是我們?cè)谄匠i_發(fā)中都習(xí)以為常的常識(shí)了,但我卻沒有從原理的角度給鐘同學(xué)分析,導(dǎo)致鐘同學(xué)茶飯不思,作為老司機(jī)的我,感到深深的自責(zé),于是我暗自下定決心,要給鐘同學(xué)一個(gè)交代。
不服跑個(gè)demo測試在方法中不加事務(wù)時(shí),每個(gè)請(qǐng)求是否會(huì)創(chuàng)建一個(gè)SqlSession:
從日志可以看出,在沒有加事務(wù)的情況下,確實(shí)是Mapper的每次請(qǐng)求數(shù)據(jù)庫,都會(huì)創(chuàng)建一個(gè)SqlSession與數(shù)據(jù)庫交互,下面我們?cè)倏纯醇恿耸聞?wù)的情況:
從日志可以看出,在方法中加了事務(wù)后,兩次請(qǐng)求只創(chuàng)建了一個(gè)SqlSession,再次證明了我上面的回答,但是僅僅這樣回答是體現(xiàn)完全不出一個(gè)老司機(jī)應(yīng)有的職業(yè)素養(yǎng)的,所以,我要發(fā)車了。
什么是SqlSession在發(fā)車之前,我們必須得先搞明白,什么是SqlSession?
簡單來說,SqlSession是Mybatis工作的最頂層API會(huì)話接口,所有的數(shù)據(jù)庫操作都經(jīng)由它來實(shí)現(xiàn),由于它就是一個(gè)會(huì)話,即一個(gè)SqlSession應(yīng)該僅存活于一個(gè)業(yè)務(wù)請(qǐng)求中,也可以說一個(gè)SqlSession對(duì)應(yīng)這一次數(shù)據(jù)庫會(huì)話,它不是永久存活的,每次訪問數(shù)據(jù)庫時(shí)都需要?jiǎng)?chuàng)建它。
因此,SqlSession并不是線程安全,每個(gè)線程都應(yīng)該有它自己的 SqlSession 實(shí)例,千萬不能將一個(gè)SqlSession搞成單例形式,或者靜態(tài)域和實(shí)例變量的形式都會(huì)導(dǎo)致SqlSession出現(xiàn)事務(wù)問題,這也就是為什么多個(gè)請(qǐng)求同一個(gè)事務(wù)中會(huì)共用一個(gè)SqlSession會(huì)話的原因,我們從SqlSession的創(chuàng)建過程來說明這點(diǎn):
從Configuration配置類中拿到Environment數(shù)據(jù)源;
從數(shù)據(jù)源中獲取TransactionFactory和DataSource,并創(chuàng)建一個(gè)Transaction連接管理對(duì)象;
創(chuàng)建Executor對(duì)象(SqlSession只是所有操作的門面,真正要干活的是Executor,它封裝了底層JDBC所有的操作細(xì)節(jié));
創(chuàng)建SqlSession會(huì)話。
每次創(chuàng)建一個(gè)SqlSession會(huì)話,都會(huì)伴隨創(chuàng)建一個(gè)專屬SqlSession的連接管理對(duì)象,如果SqlSession共享,就會(huì)出現(xiàn)事務(wù)問題。
從源碼的角度分析源碼分析從哪一步作為入口呢?如果是看過我之前寫的那幾篇關(guān)于mybatis的源碼分析,我相信你不會(huì)在Mybatis源碼前磨磨蹭蹭,遲遲找不到入口。
在之前的文章里已經(jīng)說過了,Mapper的實(shí)現(xiàn)類是一個(gè)代理,真正執(zhí)行邏輯的是MapperProxy.invoke(),該方法最終執(zhí)行的是sqlSessionTemplate。
org.mybatis.spring.SqlSessionTemplate:
private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property "sqlSessionFactory" is required"); notNull(executorType, "Property "executorType" is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
這個(gè)是創(chuàng)建SqlSessionTemplate的最終構(gòu)造方法,可以看出sqlSessionTemplate中用到了SqlSession,是SqlSessionInterceptor實(shí)現(xiàn)的一個(gè)動(dòng)態(tài)代理類,所以我們直接深入要塞:
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); 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) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
Mapper所有的方法,最終都會(huì)用這個(gè)方法來處理所有的數(shù)據(jù)庫操作,茶飯不思的鐘同學(xué)眼神迷離不知道是不是自暴自棄導(dǎo)致擼多了,眼神空洞地望著我,問我spring整合mybatis和mybatis多帶帶使用是否有區(qū)別,其實(shí)沒區(qū)別,區(qū)別就是spring封裝了所有處理細(xì)節(jié),你就不用寫大量的冗余代碼,專注于業(yè)務(wù)開發(fā)。
該動(dòng)態(tài)代理方法主要做了以下處理:
根據(jù)當(dāng)前條件獲取一個(gè)SqlSession,此時(shí)SqlSession可能是新創(chuàng)建的也有可能是獲取到上一次請(qǐng)求的SqlSession;
反射執(zhí)行SqlSession方法,再判斷當(dāng)前會(huì)話是否是一個(gè)事務(wù),如果是一個(gè)事務(wù),則不commit;
如果此時(shí)拋出異常,判斷如果是PersistenceExceptionTranslator且不為空,那么就關(guān)閉當(dāng)前會(huì)話,并且將sqlSession置為空防止finally重復(fù)關(guān)閉,PersistenceExceptionTranslator是spring定義的數(shù)據(jù)訪問集成層的異常接口;
finally無論怎么執(zhí)行結(jié)果如何,只要當(dāng)前會(huì)話不為空,那么就會(huì)執(zhí)行關(guān)閉當(dāng)前會(huì)話操作,關(guān)閉當(dāng)前會(huì)話操作又會(huì)根據(jù)當(dāng)前會(huì)話是否有事務(wù)來決定會(huì)話是釋放還是直接關(guān)閉。
org.mybatis.spring.SqlSessionUtils#getSqlSession:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
是不是看到了不服跑個(gè)demo時(shí)看到的日志“Creating a new SqlSession”了,那么證明我直接深入的地方挺準(zhǔn)確的,沒有絲毫誤差。在這個(gè)方法當(dāng)中,首先是從TransactionSynchronizationManager(以下稱當(dāng)前線程事務(wù)管理器)獲取當(dāng)前線程threadLocal是否有SqlSessionHolder,如果有就從SqlSessionHolder取出當(dāng)前SqlSession,如果當(dāng)前線程threadLocal沒有SqlSessionHolder,就從sessionFactory中創(chuàng)建一個(gè)SqlSession,具體的創(chuàng)建步驟上面已經(jīng)說過了,接著注冊(cè)會(huì)話到當(dāng)前線程threadLocal中。
先來看看當(dāng)前線程事務(wù)管理器的結(jié)構(gòu):
public abstract class TransactionSynchronizationManager { // ... // 存儲(chǔ)當(dāng)前線程事務(wù)資源,比如Connection、session等 private static final ThreadLocal
這是spring的一個(gè)當(dāng)前線程事務(wù)管理器,它允許將當(dāng)前資源存儲(chǔ)到當(dāng)前線程ThreadLocal中,從前面也可看出SqlSessionHolder是保存在resources中。
org.mybatis.spring.SqlSessionUtils#registerSessionHolder:
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; // 判斷當(dāng)前是否有事務(wù) if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); // 判斷當(dāng)前環(huán)境配置的事務(wù)管理工廠是否是SpringManagedTransactionFactory(默認(rèn)) if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]"); } holder = new SqlSessionHolder(session, executorType, exceptionTranslator); // 綁定當(dāng)前SqlSessionHolder到線程ThreadLocal中 TransactionSynchronizationManager.bindResource(sessionFactory, holder); // 注冊(cè)SqlSession同步回調(diào)器 TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); // 會(huì)話使用次數(shù)+1 holder.requested(); } else { if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); } } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } } }
注冊(cè)SqlSession到當(dāng)前線程事務(wù)管理器的條件首先是當(dāng)前環(huán)境中有事務(wù),否則不注冊(cè),判斷是否有事務(wù)的條件是synchronizations的ThreadLocal是否為空:
public static boolean isSynchronizationActive() { return (synchronizations.get() != null); }
每當(dāng)我們開啟一個(gè)事務(wù),會(huì)調(diào)用initSynchronization()方法進(jìn)行初始化synchronizations,以激活當(dāng)前線程事務(wù)管理器。
public static void initSynchronization() throws IllegalStateException { if (isSynchronizationActive()) { throw new IllegalStateException("Cannot activate transaction synchronization - already active"); } logger.trace("Initializing transaction synchronization"); synchronizations.set(new LinkedHashSet()); }
所以當(dāng)前有事務(wù)時(shí),會(huì)注冊(cè)SqlSession到當(dāng)前線程ThreadLocal中。
Mybatis自己也實(shí)現(xiàn)了一個(gè)自定義的事務(wù)同步回調(diào)器SqlSessionSynchronization,在注冊(cè)SqlSession的同時(shí),也會(huì)將SqlSessionSynchronization注冊(cè)到當(dāng)前線程事務(wù)管理器中,它的作用是根據(jù)事務(wù)的完成狀態(tài)回調(diào)來處理線程資源,即當(dāng)前如果有事務(wù),那么當(dāng)每次狀態(tài)發(fā)生時(shí)就會(huì)回調(diào)事務(wù)同步器,具體細(xì)節(jié)可移步至Spring的org.springframework.transaction.support包。
回到SqlSessionInterceptor代理類的邏輯,發(fā)現(xiàn)判斷會(huì)話是否需要提交要調(diào)用以下方法:
org.mybatis.spring.SqlSessionUtils#isSqlSessionTransactional:
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); return (holder != null) && (holder.getSqlSession() == session); }
取決于當(dāng)前SqlSession是否為空并且判斷當(dāng)前SqlSession是否與ThreadLocal中的SqlSession相等,前面也分析了,如果當(dāng)前沒有事務(wù),SqlSession是不會(huì)保存到事務(wù)同步管理器的,即沒有事務(wù),會(huì)話提交。
org.mybatis.spring.SqlSessionUtils#closeSqlSession:
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if ((holder != null) && (holder.getSqlSession() == session)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Releasing transactional SqlSession [" + session + "]"); } holder.released(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Closing non transactional SqlSession [" + session + "]"); } session.close(); } }
方法無論執(zhí)行結(jié)果如何都需要執(zhí)行關(guān)閉會(huì)話邏輯,這里的判斷也是判斷當(dāng)前是否有事務(wù),如果SqlSession在事務(wù)當(dāng)中,則減少引用次數(shù),沒有真實(shí)關(guān)閉會(huì)話。如果當(dāng)前會(huì)話不存在事務(wù),則直接關(guān)閉會(huì)話。
寫在最后雖說鐘同學(xué)問了我一個(gè)Mybatis的問題,我卻中了Spring的圈套,猛然發(fā)現(xiàn)整個(gè)事務(wù)鏈路都處在Spring的管控當(dāng)中,這里涉及到了Spring的自定義事務(wù)的一些機(jī)制,其中當(dāng)前線程事務(wù)管理器是整個(gè)事務(wù)的核心與中軸,當(dāng)前有事務(wù)時(shí),會(huì)初始化當(dāng)前線程事務(wù)管理器的synchronizations,即激活了當(dāng)前線程同步管理器,當(dāng)Mybatis訪問數(shù)據(jù)庫會(huì)首先從當(dāng)前線程事務(wù)管理器獲取SqlSession,如果不存在就會(huì)創(chuàng)建一個(gè)會(huì)話,接著注冊(cè)會(huì)話到當(dāng)前線程事務(wù)管理器中,如果當(dāng)前有事務(wù),則會(huì)話不關(guān)閉也不commit,Mybatis還自定義了一個(gè)TransactionSynchronization,用于事務(wù)每次狀態(tài)發(fā)生時(shí)回調(diào)處理。
鐘同學(xué),this is for you!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/73803.html
摘要:微信公眾號(hào)后端進(jìn)階,專注后端技術(shù)分享框架分布式中間件服務(wù)治理等等。 微信公眾號(hào)「后端進(jìn)階」,專注后端技術(shù)分享:Java、Golang、WEB框架、分布式中間件、服務(wù)治理等等。 老司機(jī)傾囊相授,帶你一路進(jìn)階,來不及解釋了快上車! 公司的某些業(yè)務(wù)用到了數(shù)據(jù)庫的悲觀鎖 for update,但有些同事沒有把 for update 放在 Spring 事務(wù)中執(zhí)行,在并發(fā)場景下發(fā)生了嚴(yán)重的線程阻...
摘要:一級(jí)緩存介紹及相關(guān)配置。在這個(gè)章節(jié),我們學(xué)習(xí)如何使用的一級(jí)緩存。一級(jí)緩存實(shí)驗(yàn)配置完畢后,通過實(shí)驗(yàn)的方式了解一級(jí)緩存的效果。源碼分析了解具體的工作流程后,我們隊(duì)查詢相關(guān)的核心類和一級(jí)緩存的源碼進(jìn)行走讀。 我,后端Java工程師,現(xiàn)在美團(tuán)點(diǎn)評(píng)工作。愛健身,愛技術(shù),也喜歡寫點(diǎn)文字。個(gè)人網(wǎng)站: http://kailuncen.me公眾號(hào): KailunTalk (凱倫說) 前言 本文主要涉及...
摘要:一級(jí)緩存介紹及相關(guān)配置。在這個(gè)章節(jié),我們學(xué)習(xí)如何使用的一級(jí)緩存。一級(jí)緩存實(shí)驗(yàn)配置完畢后,通過實(shí)驗(yàn)的方式了解一級(jí)緩存的效果。源碼分析了解具體的工作流程后,我們隊(duì)查詢相關(guān)的核心類和一級(jí)緩存的源碼進(jìn)行走讀。 我,后端Java工程師,現(xiàn)在美團(tuán)點(diǎn)評(píng)工作。愛健身,愛技術(shù),也喜歡寫點(diǎn)文字。個(gè)人網(wǎng)站: http://kailuncen.me公眾號(hào): KailunTalk (凱倫說) 前言 本文主要涉及...
閱讀 1626·2021-10-14 09:43
閱讀 5503·2021-09-07 10:21
閱讀 1275·2019-08-30 15:56
閱讀 2123·2019-08-30 15:53
閱讀 1231·2019-08-30 15:44
閱讀 2010·2019-08-30 15:44
閱讀 1320·2019-08-29 17:24
閱讀 752·2019-08-29 15:19