项目问题
现象:同一段代码,因为前后两个不同的人写的,sql一样, 只有返回的对象有差别。在不同的地方执行,返回的对象list数量不一致。
解决问题:首先对比了一下两段sql,同时对数据参数也debug了一下,完全一致,但是线上暴出来的问题,还是存在。在这里已经快崩溃了,这么简单的一个问题,折腾过去折腾过来都没找到问题所在。
mybatis缓存介绍
一级缓存
作用域在session级别,当sqlsession close之后,缓存就会失效。默认是开启的,不能关闭。
代码测试:
@TestpublicvoidtestSqlSession()throwsException{SqlSessionHelper.init("xxxx-jdbc","config/mybatis/mybatis.xml");finalSqlSessionsession=SqlSessionHelper.openSqlSession(true);TestSqlSessionMappermapper=session.getMapper(TestSqlSessionMapper.class);System.out.println(Thread.currentThread().getName()+"--- before ---"+mapper.selectTableByTableName("桌001"));// 参数改变,需要重新查询// session.clearCache();System.out.println(Thread.currentThread().getName()+"--- after ---"+mapper.selectTableByTableName("桌001"));}
终端输出如下:
2018-05-2213:39:09,828[main]INFOcom.test.interceptor.PerformanceInterceptor-com.test.xxx.mappers.TestSqlSessionMapper.selectTableByTableNameselectzuot_idfromzuotWHEREzuot_mingc='桌001'执行时间:[24ms]main---before---00010008main---after---00010008
可以发现,2次查询,只有一次是从数据库获取的数据。 把上面代码的注释打开,输出如下:
2018-05-2213:38:03,758[main]INFOcom.test.interceptor.PerformanceInterceptor-com.test.xxx.mappers.TestSqlSessionMapper.selectTableByTableNameselectzuot_idfromzuotWHEREzuot_mingc='桌001'执行时间:[24ms]main---before---000100082018-05-2213:38:03,764[main]INFOcom.test.interceptor.PerformanceInterceptor-com.test.xxx.mappers.TestSqlSessionMapper.selectTableByTableNameselectzuot_idfromzuotWHEREzuot_mingc='桌001'执行时间:[6ms]main---after---00010008二级缓存
mapper级别的缓存,即多个sqlsession用同一个mapper的sql语句去操作数据库时,得到的数据会存在二级缓存。Mybatis默认关闭二级缓存,可以在setting全局参数中配置开启二级缓存
代码测试:
@TestpublicvoidtestL2Cache()throwsException{SqlSessionFactorysessionFactory=newSqlSessionFactoryBuilder().build(Resources.getResourceAsStream("config/mybatis/mybatis.xml"));SqlSessionsession1=sessionFactory.openSession();SqlSessionsession2=sessionFactory.openSession();SqlSessionsession3=sessionFactory.openSession();TestSqlSessionMappermapper1=session1.getMapper(TestSqlSessionMapper.class);TestSqlSessionMappermapper2=session2.getMapper(TestSqlSessionMapper.class);TestSqlSessionMappermapper3=session3.getMapper(TestSqlSessionMapper.clasSystem.out.println("sqlsession1 --- "+mapper1.selectTableByTableName("桌001"));session1.close();System.out.println("sqlsession2 --- "+mapper2.selectTableByTableName("桌001"));System.out.println("sqlsession3 --- "+mapper3.selectTableByTableName("桌001"));}
注意:这里的session1.close()是为了刷新缓存,,session1的查询结果才会更新到缓存。 mybatis开启二级缓存的配置: mybatis配置文件,如果不配置该项,是默认开启的。
mapper.xml文件配置二级缓存开启,且需要在statement中配置useCache=true(本地试验情况,这个配置可以不需要,但不能配置为false)
selectzuot_idfromzuotWHEREzuot_mingc=#{tableName}
此时终端输出日志:
2018-05-2213:46:49,704[main]INFOcom.test.interceptor.PerformanceInterceptor-com.test.mappers.TestSqlSessionMapper.selectTableByTableNameselectzuot_idfromzuotWHEREzuot_mingc='桌001'执行时间:[26ms]sqlsession1---00010008sqlsession2---00010008sqlsession3---00010008
可见该sql同样只到数据库执行了一遍。如果修改配置,去掉二级缓存的配置,可以采用
全局的mybatis二级缓存配置为false
不开启
useCache=false
此时终端输出如下:
2018-05-2214:54:23,516[main]INFOcom.test.interceptor.PerformanceInterceptor-com.test.mappers.TestSqlSessionMapper.selectTableByTableNameselectzuot_idfromzuotWHEREzuot_mingc='桌001'执行时间:[154ms]sqlsession1---000100082018-05-2214:54:23,561[main]INFOcom.test.interceptor.PerformanceInterceptor-com.test.mappers.TestSqlSessionMapper.selectTableByTableNameselectzuot_idfromzuotWHEREzuot_mingc='桌001'执行时间:[37ms]sqlsession2---000100082018-05-2214:54:23,598[main]INFOcom.test.interceptor.PerformanceInterceptor-com.test.mappers.TestSqlSessionMapper.selectTableByTableNameselectzuot_idfromzuotWHEREzuot_mingc='桌001'执行时间:[37ms]sqlsession3---00010008从源码角度分析
mybatis所有的数据库执行操作,都是交给底层的executor抽象执行。我们从mybatis的openSqlsession方法入手开始分析:调用链如下
SqlSessionFactory.openSession()->DefaultSqlSessionFactory.openSession()->DefaultSqlSessionFactory.openSessionFromDataSource(ExecutorTypeexecType,TransactionIsolationLevellevel,booleanautoCommit)->configuration.newExecutor(tx,execType)
下面是configuration的newExecutor方法
publicExecutornewExecutor(Transactiontransaction,ExecutorTypeexecutorType){executorType=executorType==null?defaultExecutorType:executorType;executorType=executorType==null?ExecutorType.SIMPLE:executorType;Executorexecutor;if(ExecutorType.BATCH==executorType){executor=newBatchExecutor(this,transaction);}elseif(ExecutorType.REUSE==executorType){executor=newReuseExecutor(this,transaction);}else{executor=newSimpleExecutor(this,transaction);}if(cacheEnabled){executor=newCachingExecutor(executor);}executor=(Executor)interceptorChain.pluginAll(executor);returnexecutor;}
可以看到,configuration会根据ExecutorType的类型(simple、reuse、batch)生成不同的executor,这里我们只关心simple的executor。
if(cacheEnabled){executor=newCachingExecutor(executor);}
这里会根据cacheEnabled的配置,是否返回CachingExecutor的executor,这个应该是解析mybatis配置文件中,是否开启了二级缓存。如果开启了二级缓存,那么生成的executor就是CachingExecutor。观察CachingExecutor的构造函数可知,它本地维护了一个SimpleExecutor,作为静态代理对象负责对数据库的操作,而CachingExecutor本身应该就是处理缓存相关的操作。
再看看executor的query方法
可以看到,该方法的执行根据是否开启二级缓存,调用不同的实现。我们看BaseExecutor的实现
try{queryStack++;list=resultHandler==null?(List)localCache.getObject(key):null;if(list!=null){handleLocallyCachedOutputParameters(ms,key,parameter,boundSql);}else{list=queryFromDatabase(ms,parameter,rowBounds,resultHandler,key,boundSql);}}finally{queryStack--;}
只截取了关键的一部分,查询时会先从localCache中获取数据,如果查询结果为空,就会去数据库查询。
在关闭sqlsession时,close()方法会调用方法rollback(),会首先清空一级缓存的cache,印证了我们closeSqlsession时,会清空缓存的说法。
publicvoidrollback(booleanrequired)throwsSQLException{if(!closed){try{clearLocalCache();flushStatements(true);}finally{if(required){transaction.rollback();}}}}
如果是二级缓存,在close时会提交查询内容到二级缓存
@Overridepublicvoidclose(booleanforceRollback){try{//issues #499, #524 and #573if(forceRollback){tcm.rollback();}else{tcm.commit();}}finally{delegate.close(forceRollback);}}
领取专属 10元无门槛券
私享最新 技术干货