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缓存的原因分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!