spring 整合mybatis后用不上session緩存的原因分析
因?yàn)橐恢庇胹pring整合了mybatis,所以很少用到mybatis的session緩存。 習(xí)慣是本地緩存自己用map寫(xiě)或者引入第三方的本地緩存框架ehcache,Guava
所以提出來(lái)糾結(jié)下
實(shí)驗(yàn)下(spring整合mybatis略,網(wǎng)上一堆),先看看mybatis級(jí)別的session的緩存
放出打印sql語(yǔ)句
configuration.xml 加入
<settings> <!-- 打印查詢(xún)語(yǔ)句 --> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings>
測(cè)試源代碼如下:
dao類(lèi)
/** * 測(cè)試spring里的mybatis為啥用不上緩存 * * @author 何錦彬 2017.02.15 */ @Component public class TestDao { private Logger logger = Logger.getLogger(TestDao.class.getName()); @Autowired private SqlSessionTemplate sqlSessionTemplate; @Autowired private SqlSessionFactory sqlSessionFactory; /** * 兩次SQL * * @param id * @return */ public TestDto selectBySpring(String id) { TestDto testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id); testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id); return testDto; } /** * 一次SQL * * @param id * @return */ public TestDto selectByMybatis(String id) { SqlSession session = sqlSessionFactory.openSession(); TestDto testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id); testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id); return testDto; } }
測(cè)試service類(lèi)
@Component public class TestService { @Autowired private TestDao testDao; /** * 未開(kāi)啟事務(wù)的spring Mybatis查詢(xún) */ public void testSpringCashe() { //查詢(xún)了兩次SQL testDao.selectBySpring("1"); } /** * 開(kāi)啟事務(wù)的spring Mybatis查詢(xún) */ @Transactional public void testSpringCasheWithTran() { //spring開(kāi)啟事務(wù)后,查詢(xún)1次SQL testDao.selectBySpring("1"); } /** * mybatis查詢(xún) */ public void testCash4Mybatise() { //原生態(tài)mybatis,查詢(xún)了1次SQL testDao.selectByMybatis("1"); } }
輸出結(jié)果:
testSpringCashe()方法執(zhí)行了兩次SQL, 其它都是一次
源碼追蹤:
先看mybatis里的sqlSession
跟蹤到最后 調(diào)用到 org.apache.ibatis.executor.BaseExecutor的query方法
try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; //先從緩存中取 if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); //注意里面的key是CacheKey } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }
貼下是怎么取出緩存數(shù)據(jù)的代碼
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) { if (ms.getStatementType() == StatementType.CALLABLE) { final Object cachedParameter = localOutputParameterCache.getObject(key);//從localOutputParameterCache取出緩存對(duì)象 if (cachedParameter != null && parameter != null) { final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter); final MetaObject metaParameter = configuration.newMetaObject(parameter); for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) { if (parameterMapping.getMode() != ParameterMode.IN) { final String parameterName = parameterMapping.getProperty(); final Object cachedValue = metaCachedParameter.getValue(parameterName); metaParameter.setValue(parameterName, cachedValue); } } } } }
發(fā)現(xiàn)就是從localOutputParameterCache就是一個(gè)PerpetualCache, PerpetualCache維護(hù)了個(gè)map,就是session的緩存本質(zhì)了。
重點(diǎn)可以關(guān)注下面兩個(gè)累的邏輯
PerpetualCache , 兩個(gè)參數(shù), id和map
CacheKey,map中存的key,它有覆蓋equas方法,當(dāng)獲取緩存時(shí)調(diào)用.
這種本地map緩存獲取對(duì)象的缺點(diǎn),就我踩坑經(jīng)驗(yàn)(以前我也用map去實(shí)現(xiàn)的本地緩存),就是獲取的對(duì)象非clone的,返回的兩個(gè)對(duì)象都是一個(gè)地址
而在spring中一般都是用sqlSessionTemplate,如下
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:configuration.xml" /> <property name="mapperLocations"> <list> <value>classpath*:com/hejb/sqlmap/*.xml</value> </list> </property> </bean> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg ref="sqlSessionFactory" /> </bean>
在SqlSessionTemplate中執(zhí)行SQL的session都是通過(guò)sqlSessionProxy來(lái),sqlSessionProxy的生成在構(gòu)造函數(shù)中賦值,如下:
this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());
sqlSessionProxy通過(guò)JDK的動(dòng)態(tài)代理方法生成的一個(gè)代理類(lèi),主要邏輯在InvocationHandler對(duì)執(zhí)行的方法進(jìn)行了前后攔截,主要邏輯在invoke中,包好了每次執(zhí)行對(duì)sqlsesstion的創(chuàng)建,common,關(guān)閉
代碼如下:
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 每次執(zhí)行前都創(chuàng)建一個(gè)新的sqlSession SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 執(zhí)行方法 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); } } } }
因?yàn)槊看味歼M(jìn)行創(chuàng)建,所以就用不上sqlSession的緩存了.
對(duì)于開(kāi)啟了事務(wù)為什么可以用上呢, 跟入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); // 首先從SqlSessionHolder里取出session 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; }
在里面維護(hù)了個(gè)SqlSessionHolder,關(guān)聯(lián)了事務(wù)與session,如果存在則直接取出,否則則新建個(gè)session,所以在有事務(wù)的里,每個(gè)session都是同一個(gè),故能用上緩存了
以上所述是小編給大家介紹的spring 整合mybatis后用不上session緩存的原因分析,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
詳解Spring Boot Oauth2緩存UserDetails到Ehcache
這篇文章主要介紹了詳解Spring Boot Oauth2緩存UserDetails到Ehcache,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08SpringBoot項(xiàng)目實(shí)用功能之實(shí)現(xiàn)自定義參數(shù)解析器
這篇文章主要介紹了SpringBoot項(xiàng)目實(shí)用功能之實(shí)現(xiàn)自定義參數(shù)解析器,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08使用Easyexcel實(shí)現(xiàn)不同場(chǎng)景的數(shù)據(jù)導(dǎo)出功能
這篇文章主要為大家詳細(xì)介紹了如何在不同場(chǎng)景下使用Easyexcel實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03SpringBoot 過(guò)濾器, 攔截器, 監(jiān)聽(tīng)器的具體使用
本文主要介紹了SpringBoot 過(guò)濾器, 攔截器, 監(jiān)聽(tīng)器的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05SpringBoot中的JPA(Java?Persistence?API)詳解
這篇文章主要介紹了SpringBoot中的JPA(Java?Persistence?API)詳解,JPA用于將?Java?對(duì)象映射到關(guān)系型數(shù)據(jù)庫(kù)中,它提供了一種面向?qū)ο蟮姆绞絹?lái)操作數(shù)據(jù)庫(kù),使得開(kāi)發(fā)者可以更加方便地進(jìn)行數(shù)據(jù)持久化操作,需要的朋友可以參考下2023-07-07