spring 整合mybatis后用不上session缓存的原因分析
因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,Guava
所以提出来纠结下
实验下(spring整合mybatis略,网上一堆),先看看mybatis级别的session的缓存
放出打印sql语句
configuration.xml加入
<settings> <!--打印查询语句--> <settingname="logImpl"value="STDOUT_LOGGING"/> </settings>
测试源代码如下:
dao类
/** *测试spring里的mybatis为啥用不上缓存 * *@author何锦彬2017.02.15 */ @Component publicclassTestDao{ privateLoggerlogger=Logger.getLogger(TestDao.class.getName()); @Autowired privateSqlSessionTemplatesqlSessionTemplate; @Autowired privateSqlSessionFactorysqlSessionFactory; /** *两次SQL * *@paramid *@return */ publicTestDtoselectBySpring(Stringid){ TestDtotestDto=(TestDto)sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey",id); testDto=(TestDto)sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey",id); returntestDto; } /** *一次SQL * *@paramid *@return */ publicTestDtoselectByMybatis(Stringid){ SqlSessionsession=sqlSessionFactory.openSession(); TestDtotestDto=session.selectOne("com.hejb.TestDto.selectByPrimaryKey",id); testDto=session.selectOne("com.hejb.TestDto.selectByPrimaryKey",id); returntestDto; } }
测试service类
@Component publicclassTestService{ @Autowired privateTestDaotestDao; /** *未开启事务的springMybatis查询 */ publicvoidtestSpringCashe(){ //查询了两次SQL testDao.selectBySpring("1"); } /** *开启事务的springMybatis查询 */ @Transactional publicvoidtestSpringCasheWithTran(){ //spring开启事务后,查询1次SQL testDao.selectBySpring("1"); } /** *mybatis查询 */ publicvoidtestCash4Mybatise(){ //原生态mybatis,查询了1次SQL testDao.selectByMybatis("1"); } }
输出结果:
testSpringCashe()方法执行了两次SQL,其它都是一次
源码追踪:
先看mybatis里的sqlSession
跟踪到最后调用到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); }
贴下是怎么取出缓存数据的代码
privatevoidhandleLocallyCachedOutputParameters(MappedStatementms,CacheKeykey,Objectparameter,BoundSqlboundSql){ if(ms.getStatementType()==StatementType.CALLABLE){ finalObjectcachedParameter=localOutputParameterCache.getObject(key);//从localOutputParameterCache取出缓存对象 if(cachedParameter!=null&¶meter!=null){ finalMetaObjectmetaCachedParameter=configuration.newMetaObject(cachedParameter); finalMetaObjectmetaParameter=configuration.newMetaObject(parameter); for(ParameterMappingparameterMapping:boundSql.getParameterMappings()){ if(parameterMapping.getMode()!=ParameterMode.IN){ finalStringparameterName=parameterMapping.getProperty(); finalObjectcachedValue=metaCachedParameter.getValue(parameterName); metaParameter.setValue(parameterName,cachedValue); } } } } }
发现就是从localOutputParameterCache就是一个PerpetualCache,PerpetualCache维护了个map,就是session的缓存本质了。
重点可以关注下面两个累的逻辑
PerpetualCache,两个参数,id和map
CacheKey,map中存的key,它有覆盖equas方法,当获取缓存时调用.
这种本地map缓存获取对象的缺点,就我踩坑经验(以前我也用map去实现的本地缓存),就是获取的对象非clone的,返回的两个对象都是一个地址
而在spring中一般都是用sqlSessionTemplate,如下
<beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"> <propertyname="dataSource"ref="dataSource"/> <propertyname="configLocation"value="classpath:configuration.xml"/> <propertyname="mapperLocations"> <list> <value>classpath*:com/hejb/sqlmap/*.xml</value> </list> </property> </bean> <beanid="sqlSessionTemplate"class="org.mybatis.spring.SqlSessionTemplate"> <constructor-argref="sqlSessionFactory"/> </bean>
在SqlSessionTemplate中执行SQL的session都是通过sqlSessionProxy来,sqlSessionProxy的生成在构造函数中赋值,如下:
this.sqlSessionProxy=(SqlSession)newProxyInstance( SqlSessionFactory.class.getClassLoader(), newClass[]{SqlSession.class}, newSqlSessionInterceptor());
sqlSessionProxy通过JDK的动态代理方法生成的一个代理类,主要逻辑在InvocationHandler对执行的方法进行了前后拦截,主要逻辑在invoke中,包好了每次执行对sqlsesstion的创建,common,关闭
代码如下:
privateclassSqlSessionInterceptorimplementsInvocationHandler{ @Override publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{ //每次执行前都创建一个新的sqlSession SqlSessionsqlSession=getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try{ //执行方法 Objectresult=method.invoke(sqlSession,args); if(!isSqlSessionTransactional(sqlSession,SqlSessionTemplate.this.sqlSessionFactory)){ //forcecommitevenonnon-dirtysessionsbecausesomedatabasesrequire //acommit/rollbackbeforecallingclose() sqlSession.commit(true); } returnresult; }catch(Throwablet){ Throwableunwrapped=unwrapThrowable(t); if(SqlSessionTemplate.this.exceptionTranslator!=null&&unwrappedinstanceofPersistenceException){ //releasetheconnectiontoavoidadeadlockifthetranslatorisnoloaded.Seeissue#22 closeSqlSession(sqlSession,SqlSessionTemplate.this.sqlSessionFactory); sqlSession=null; Throwabletranslated=SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped); if(translated!=null){ unwrapped=translated; } } throwunwrapped; }finally{ if(sqlSession!=null){ closeSqlSession(sqlSession,SqlSessionTemplate.this.sqlSessionFactory); } } } }
因为每次都进行创建,所以就用不上sqlSession的缓存了.
对于开启了事务为什么可以用上呢,跟入getSqlSession方法
如下:
publicstaticSqlSessiongetSqlSession(SqlSessionFactorysessionFactory,ExecutorTypeexecutorType,PersistenceExceptionTranslatorexceptionTranslator){ notNull(sessionFactory,NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType,NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolderholder=(SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory); //首先从SqlSessionHolder里取出session SqlSessionsession=sessionHolder(executorType,holder); if(session!=null){ returnsession; } if(LOGGER.isDebugEnabled()){ LOGGER.debug("CreatinganewSqlSession"); } session=sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory,executorType,exceptionTranslator,session); returnsession; }
在里面维护了个SqlSessionHolder,关联了事务与session,如果存在则直接取出,否则则新建个session,所以在有事务的里,每个session都是同一个,故能用上缓存了
以上所述是小编给大家介绍的spring整合mybatis后用不上session缓存的原因分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!