首页
学习
活动
专区
工具
TVP
发布

Mybatis缓存

项目问题

现象:同一段代码,因为前后两个不同的人写的,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);}}

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180522G10GB300?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券