Mybatis的SqlSession和一級(jí)緩存的失效原因分析及解決
SqlSession解讀
SqlSession是什么?
SqlSession是Mybatis 中定義的,用來(lái)表示與關(guān)系數(shù)據(jù)庫(kù)的一次會(huì)話,會(huì)話定義了各種具體的操作,查詢、數(shù)據(jù)更新(包含保存、更新、刪除)操作。而這些操作都在與數(shù)據(jù)庫(kù)建立會(huì)話的基礎(chǔ)上進(jìn)行的。
SqlSession 可以看作是對(duì)Connection 更加高級(jí)的抽象,從其方法上更加可以看出他具有更加明顯的操作特征。
SqlSession分類
mybatis的SqlSession有三種:
DefaultSqlSession、SqlSessionManager、SqlSessionTemplate,前兩者是mybtais默認(rèn)情況下使用的,第三種主要用到mybatis和spring整合的時(shí)候。
SqlSession的創(chuàng)建
那么SqlSession 是如何被創(chuàng)建的?
在學(xué)習(xí)Mybatis時(shí),我們常??吹降?SqlSession 創(chuàng)建方式是 SqlSessionFactory.openSession() ,那么我們就從它作為切入點(diǎn),先來(lái)看看 SqlSessionFactory.openSession() 的方法源碼(需要注意的是這里是實(shí)現(xiàn)類DefaultSqlSessionFactory )
代碼如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 獲取環(huán)境配置
final Environment environment = configuration.getEnvironment();
// 創(chuàng)建事務(wù)
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 創(chuàng)建執(zhí)行器
final Executor executor = configuration.newExecutor(tx, execType);
// 創(chuàng)建sqlsession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}通過(guò)源碼我們知道每次 SqlSession(準(zhǔn)確地說(shuō)是 DefaultSqlSession )的創(chuàng)建都會(huì)有一個(gè) Transaction 事務(wù)對(duì)象 的生成。也就是說(shuō):
- 一個(gè)事務(wù) Transaction 對(duì)象與一個(gè) SqlSession 對(duì)象 是一一對(duì)應(yīng)的關(guān)系。
- 同一個(gè)SqlSession 不管執(zhí)行多少次數(shù)據(jù)庫(kù)操作。只要沒(méi)有執(zhí)行close,那么整個(gè)操作都是在同一個(gè) Transaction 中執(zhí)行的。
但需要注意的是,我們整合Spring之后用到的其實(shí)都是 SqlSessionTemplate ,與這里的 DefaultSqlSession 不是同一個(gè)SqlSession對(duì)象,不懂的看上面的。
為什么和Spring整合后的SqlSession一級(jí)緩存偶爾會(huì)失效?
我們都知道m(xù)ybatis有一級(jí)緩存和二級(jí)緩存。一級(jí)緩存是SqlSession級(jí)別的緩存,在操作數(shù)據(jù)庫(kù)時(shí),每個(gè)SqlSession類的實(shí)例對(duì)象緩存的數(shù)據(jù)區(qū)域(Map)可以用于存儲(chǔ)緩存數(shù)據(jù),不同的SqlSession類的實(shí)例對(duì)象緩存的數(shù)據(jù)區(qū)域是互不影響的。
- 一級(jí)緩存工作原理圖:

二級(jí)緩存是Mapper級(jí)別的緩存,多個(gè)SqlSession實(shí)例對(duì)象可以共用二級(jí)緩存,二級(jí)緩存是跨SqlSession的。
- Mybatis緩存模式圖如下:

我們知道在和Mybatis和Spring的整合中不管是創(chuàng)建MapperProxy 的 SqlSession 還是 MapperMethod中調(diào)用的SqlSession其實(shí)都是** SqlSessionTemplate **。
SqlSessionTemplate的神秘面紗
如果你閱讀了上面的鏈接文章,就知道 每創(chuàng)建一個(gè) MapperFactoryBean 就會(huì)創(chuàng)建一個(gè) SqlSessionTemplate 對(duì)象,而 MapperFactoryBean 在獲取 MapperProxy 時(shí)會(huì)將 SqlSessionTemplate 傳遞到 MapperProxy中。 也就是說(shuō) SqlSessionTemplate 的生命周期是與 MapperProxy 的生命周期是一致的。
SqlSessionTemplate 內(nèi)部維護(hù)了一個(gè) sqlSessionProxy ,而 sqlSessionProxy 是通過(guò)動(dòng)態(tài)代理創(chuàng)建的一個(gè) SqlSession 對(duì)象, SqlSessionTemplate 的 數(shù)據(jù)庫(kù)操作方法 insert/update 等等都是委托 sqlSessionProxy 來(lái)執(zhí)行的,我們看一下它的構(gòu)造方法:
// 構(gòu)造方法
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
...省略無(wú)關(guān)緊要的代碼
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}我們會(huì)發(fā)現(xiàn)這個(gè)類也繼承了SqlSession接口,我們選擇一個(gè)查詢方法來(lái)深入看一下為什么mybatis一級(jí)緩存偶爾會(huì)失效,我們進(jìn)入到他的selectList方法,看下他的實(shí)現(xiàn)邏輯:
public class SqlSessionTemplate implements SqlSession, DisposableBean {...}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}我們發(fā)現(xiàn),這個(gè)方法內(nèi)部?jī)?nèi)部的查詢又交給了一層代理,由這一層代理去真正執(zhí)行的查詢操作,而這個(gè)代理就是在SqlSessionTemplate創(chuàng)建的時(shí)候進(jìn)行設(shè)置的。
如果熟悉動(dòng)態(tài)代理的話,就知道,我們接下來(lái)需要看的就是SqlSessionInterceptor,我們進(jìn)入到里面看一下他的實(shí)現(xiàn):
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 去獲取SqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 通過(guò)反射調(diào)用真正的處理方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// 提交數(shù)據(jù)
sqlSession.commit(true);
}
// 返回查詢的數(shù)據(jù)
return result;
} catch (Throwable t) {
...省略無(wú)關(guān)緊要的代碼
} finally {
if (sqlSession != null) {
// 關(guān)閉SqlSession的連接
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}整個(gè) invoke 分5個(gè)步驟:
- 根據(jù)條件獲取一個(gè)SqlSession(注意此時(shí)的SqlSession 是 DefaultSqlSession ),此時(shí)的SqlSession 可能是新創(chuàng)建的,也可能是上一次的請(qǐng)求的SqlSession。
- 反射執(zhí)行 SqlSession 方法
- 判斷當(dāng)前的 SqlSession 是否由事務(wù)所管控,如果是則不commit
- 判斷如果是PersistenceExceptionTranslator且不為空,那么就關(guān)閉當(dāng)前會(huì)話,并且將sqlSession置為空防止finally重復(fù)關(guān)閉
- 只要當(dāng)前會(huì)話不為空, 那么就會(huì)關(guān)閉當(dāng)前會(huì)話操作,關(guān)閉當(dāng)前會(huì)話操作又會(huì)根據(jù)當(dāng)前會(huì)話是否有事務(wù)來(lái)決定會(huì)話是釋放還是直接關(guān)閉。
我們都知道一級(jí)緩存是SqlSession級(jí)別的緩存,那么一級(jí)緩存失效,肯定是因?yàn)镾qlSession不一致,那么我們進(jìn)入到getSqlSession方法中:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
...省略無(wú)關(guān)緊要的代碼
// 從ThreadLocal變量里面獲取到Spring的事務(wù)同步管理器
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 調(diào)用靜態(tài)方法sessionHoler 判斷是否存在符合要求的sqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 如果SqlSessionHolder中獲取的SqlSession為空,則新建一個(gè)SqlSession
session = sessionFactory.openSession(executorType);
// 判斷當(dāng)前是否存在事務(wù),將sqlSession 綁定到sqlSessionHolder 中,并放到threadLoacl 當(dāng)中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
看到這,我們應(yīng)該知道為什么Spring和MyBatis整合后,偶爾會(huì)一級(jí)緩存失效了,是因?yàn)镾pring只有在開(kāi)啟了事務(wù)之后,在同一個(gè)事務(wù)里的SqlSession會(huì)被緩存起來(lái),同一個(gè)事務(wù)中,多次查詢是可以命中緩存的!
在SqlSessionInterceptor#invoke方法里面,他在關(guān)閉的SqlSession的時(shí)候同樣對(duì)是否開(kāi)啟事務(wù)做了處理,感興趣的可以看closeSqlSession方法的源碼:
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
...省略無(wú)關(guān)緊要的代碼
SqlSessionHolder holder =
(SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 查看事務(wù)同步管理器是否存在 session
if ((holder != null) && (holder.getSqlSession() == session)) {
holder.released();
} else {
// 如果不存在就將該Session關(guān)閉掉
session.close();
}
}總結(jié)
- 同一事務(wù)中不管調(diào)用多少次 mapper里的方法 ,最終都是用得同一個(gè)sqlSession,即一個(gè)事務(wù)中使用的是同一個(gè)sqlSession。
- 同一事務(wù)中,Mybatis的一級(jí)緩存才會(huì)有效。
- 如果沒(méi)有開(kāi)啟事務(wù),調(diào)用一次mapper里的方法將會(huì)新建一個(gè)sqlSession來(lái)執(zhí)行方法。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Mybatis中如何設(shè)置sqlSession自動(dòng)提交
- MyBatis中SqlSession生命周期的使用
- 使用Mybatis時(shí)SqlSessionFactory對(duì)象總是報(bào)空指針
- MyBatis與Spring中的SqlSession詳解
- springboot3集成mybatis-plus報(bào)sqlSession異常的問(wèn)題解決
- MyBatis-plus報(bào)錯(cuò)Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required的解決方法
- 詳解MyBatis的SqlSession獲取流程
相關(guān)文章
詳解spring security 配置多個(gè)AuthenticationProvider
這篇文章主要介紹了詳解spring security 配置多個(gè)AuthenticationProvider ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
java線程池不同場(chǎng)景下使用示例經(jīng)驗(yàn)總結(jié)
這篇文章主要為大家介紹了java線程池不同場(chǎng)景如何使用的示例源碼及經(jīng)驗(yàn)總結(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
Java中保留兩位小數(shù)的四種方法實(shí)現(xiàn)實(shí)例
今天小編就為大家分享一篇關(guān)于Java中保留兩位小數(shù)的四種方法實(shí)現(xiàn)實(shí)例,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
Java設(shè)計(jì)模式之中介者模式(Mediator Pattern)簡(jiǎn)介
這篇文章主要介紹了Java設(shè)計(jì)模式之中介者模式(Mediator Pattern),需要的朋友可以參考下2014-07-07
java開(kāi)發(fā)時(shí)各類工具的使用規(guī)范
這篇文章主要介紹了java編碼時(shí)各類工具的使用規(guī)范,多人協(xié)作、共同開(kāi)發(fā)一個(gè)項(xiàng)目,如果沒(méi)有統(tǒng)一的代碼規(guī)范的話,項(xiàng)目中的每個(gè)人都按照自己的習(xí)慣率性而為,就會(huì)導(dǎo)致整個(gè)項(xiàng)目的代碼看上去雜亂無(wú)章,可讀性非常差,并且持續(xù)增加后續(xù)的維護(hù)成本。對(duì)此感興趣可以來(lái)了解一下2020-07-07

