spring 整合mybatis后用不上session缓存的原因分析
因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,guava
所以提出来纠结下
实验下(spring整合mybatis略,网上一堆),先看看mybatis级别的session的缓存
放出打印sql语句
configuration.xml 加入
<settings> <!-- 打印查询语句 --> <setting name="logimpl" value="stdout_logging" /> </settings>
测试源代码如下:
dao类
/** * 测试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; } }
测试service类
@component public class testservice { @autowired private testdao testdao; /** * 未开启事务的spring mybatis查询 */ public void testspringcashe() { //查询了两次sql testdao.selectbyspring("1"); } /** * 开启事务的spring mybatis查询 */ @transactional public void testspringcashewithtran() { //spring开启事务后,查询1次sql testdao.selectbyspring("1"); } /** * mybatis查询 */ public void testcash4mybatise() { //原生态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); }
贴下是怎么取出缓存数据的代码
private void handlelocallycachedoutputparameters(mappedstatement ms, cachekey key, object parameter, boundsql boundsql) { if (ms.getstatementtype() == statementtype.callable) { final object cachedparameter = localoutputparametercache.getobject(key);//从localoutputparametercache取出缓存对象 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); } } } } }
发现就是从localoutputparametercache就是一个perpetualcache, perpetualcache维护了个map,就是session的缓存本质了。
重点可以关注下面两个累的逻辑
perpetualcache , 两个参数, id和map
cachekey,map中存的key,它有覆盖equas方法,当获取缓存时调用.
这种本地map缓存获取对象的缺点,就我踩坑经验(以前我也用map去实现的本地缓存),就是获取的对象非clone的,返回的两个对象都是一个地址
而在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中执行sql的session都是通过sqlsessionproxy来,sqlsessionproxy的生成在构造函数中赋值,如下:
this.sqlsessionproxy = (sqlsession) newproxyinstance( sqlsessionfactory.class.getclassloader(), new class[] { sqlsession.class }, new sqlsessioninterceptor());
sqlsessionproxy通过jdk的动态代理方法生成的一个代理类,主要逻辑在invocationhandler对执行的方法进行了前后拦截,主要逻辑在invoke中,包好了每次执行对sqlsesstion的创建,common,关闭
代码如下:
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 { // 执行方法 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); } } } }
因为每次都进行创建,所以就用不上sqlsession的缓存了.
对于开启了事务为什么可以用上呢, 跟入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; }
在里面维护了个sqlsessionholder,关联了事务与session,如果存在则直接取出,否则则新建个session,所以在有事务的里,每个session都是同一个,故能用上缓存了
以上所述是小编给大家介绍的spring 整合mybatis后用不上session缓存的原因分析,希望对大家有所帮助