SqlSessionFactory SqlSessionFactory:主要作用是创建SqlSession。SqlSessionFactory的生命周期随着整个Mybatis框架的运行而运行,随着Mybatis框架的销毁而销毁。SqlSession SqlSession:主要是用来执行sql。 一级缓存 mybatis是默认开启一级缓存的。而一级缓存的作用域是SqlSession。接下来使用mybatis的查询来验证一下一级缓存。 整体代码:加载mybatis配置文件 Stringsourcemybatismybatisconfig。xml; InputStreaminputStreamResources。getResourceAsStream(source); 创建SqlSessionFactory工厂对象 SqlSessionFactoryfactorynewSqlSessionFactoryBuilder()。build(inputStream); 利用工厂对象创建session会话 SqlSessionsession1factory。openSession(); SqlSessionsession2factory。openSession(); 第一个mapper LocalMapperlocalMappersession1。getMapper(LocalMapper。class); TbLocallocallocalMapper。cc();查询出来的对象 session1。commit(); 第二个mapper LocalMapperlocalMapper1session2。getMapper(LocalMapper。class); TbLocaltbLocallocalMapper1。cc();查询出来的对象查询逻辑分析(query)Override publicListquery(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql) throwsSQLException{ 获取二级缓存 Cachecachems。getCache(); 判断二级缓存是否为空(为空就进入一级缓存。不为空就进入二级缓存) if(cache!null){ flushCacheIfRequired(ms); if(ms。isUseCache()resultHandlernull){ ensureNoOutParams(ms,boundSql); SuppressWarnings(unchecked) Listlist(List)tcm。getObject(cache,key); if(listnull){ listdelegate。query(ms,parameterObject,rowBounds,resultHandler,key,boundSql); tcm。putObject(cache,key,list);issue578and116 } returnlist; } } 具体执行一级缓存的逻辑 returndelegate。query(ms,parameterObject,rowBounds,resultHandler,key,boundSql); }blockquote 2。往一级缓存中(query()方法)中走Override publicListquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{ ErrorContext。instance()。resource(ms。getResource())。activity(executingaquery)。object(ms。getId()); if(closed){ thrownewExecutorException(Executorwasclosed。); } if(queryStack0ms。isFlushCacheRequired()){ clearLocalCache(); } Listlist; try{ queryStack; 从一级缓存中获取 listresultHandlernull?(List)localCache。getObject(key):null; if(list!null){ 如果不为空,执行的操作。然后直接返回list handleLocallyCachedOutputParameters(ms,key,parameter,boundSql); }else{ 如果为空,去数据库中查询获取 listqueryFromDatabase(ms,parameter,rowBounds,resultHandler,key,boundSql); } }finally{ queryStack; } if(queryStack0){ for(DeferredLoaddeferredLoad:deferredLoads){ deferredLoad。load(); } issue601 deferredLoads。clear(); if(configuration。getLocalCacheScope()LocalCacheScope。STATEMENT){ issue482 clearLocalCache(); } } returnlist; }blockquote 咱们仔细看这一段代码。从一级缓存中获取对象 listresultHandlernull?(List)localCache。getObject(key):null; 进入localCache。getObject()方法中。 我们看到localCache的类型是PerpetualCache,其中cache为一个map,暂时猜测一级缓存就在这个map中。那么可以得出,在sqlsession查询时,会将缓存put到PerpetualCache中的cache中。 而我们知道,一级缓存作用域是SqlSeesion,那我就可以猜测PerpetualCache对象应该是在创建SqlSeesion时创建的。 接下来进入openSession的源码分析(也就是创建SqlSession的执行逻辑),看看有没有创建PerpetualCache创建SqlSessionFactory工厂对象 SqlSessionFactoryfactorynewSqlSessionFactoryBuilder()。build(inputStream); 利用工厂对象创建session会话 SqlSessionsession1factory。openSession(); 3。openSession的执行过程分析 进入openSession一路调试进入到如下方法中,并且进入newExecutor方法中(前面的一些配置信息,暂时忽略,咱们只看和缓存相关即可)。 再进入newExecutor方法中。 在该方法中创建了一些和Executor相关的对象。调试后发现只能进入,newSimpleExcutor和newCachingExecutor中(下图)。咱们先看SimpleExcutor。咱们根据下图片右边圈出来的类继承关系图,就能知道。不管创建哪个Excutor对象,肯定会先创建父类的BaseExecutor对象。 咱再看看顶层的BaseExecutor都有哪些属性。 从这里可以看出,BaseExecutor中包含了PerpetualCache。也就是上文所解释的一级缓存主要的存储对象。 我们再看看newCachingExecutor(executor);对象。我们可以看到,CachingExecutor中将delegate设置了值,而delegate是Excutor类型。BaseExecutor继承Excutor。所以delegate属性中有BaseExecutor对象(也就是一级缓存)。 那么由此得出,在创建sqlsession时,会先创建PerpetualCache对象,在进行查询时,会将缓存put入PerpetualCache的cacha中。那么也就可以解释为什么一级缓存的作用域是sqlSeesion了。每个SqlSeesion的创建都会创建一个PerpetualCache,一个sqlsession对应一个PerpetualCache。二级缓存 二级缓存作用域是SqlSessionFactory。随着SqlSessionFactory创建而创建,销毁而销毁。继续进入查询方法如下。也就是上文中的查询(query)方法:获取二级缓存 Cachecachems。getCache(); if(cache!null){ 二级缓存不为空 flushCacheIfRequired(ms); if(ms。isUseCache()resultHandlernull){ ensureNoOutParams(ms,boundSql); SuppressWarnings(unchecked) 从tcm中获取(该tcm实际上并不是二级缓存,而是暂存区(后续commit方法中会讲解)。咱们接下来进入tcm中查看) Listlist(List)tcm。getObject(cache,key); if(listnull){ listdelegate。query(ms,parameterObject,rowBounds,resultHandler,key,boundSql); tcm。putObject(cache,key,list);issue578and116 } returnlist; } }blockquote 进入tcm。getObject()方法中 发现tcm是一个TransactionalCacheManager类型的对象。其中有个属性transactionalCaches(value值),key为Cache,value为TransactionalCache。tcm。getObject方法中的getTransactionalCache方法实际上就是创建了一个TransactionalCache对象,然后再调用TransactionalCache类中的getObject方法,那么进入TransactionalCache中的getObject方法中。如下: 1。Objectobjectdelegate。getObject(key); delegate可以看到类型为Cache。使用过mybatis二级缓存的都知道。我们可以自己实现一个Cache接口,去实现自定义缓存。而这里的getObject方法就可以进入到我们自定义二级缓存中的实现中去。这里的意思是从二级缓存中获取数据。第一次查询肯定为空。 2。entriesMissedInCache。add(key); 如果为空则将key添加到entriesMissedInCache中,key为namespacesql一些序列化的东西。标识唯一。然后返回,回到query方法 我们再进入到query方法中: 代码解释如下: Listlist(List)tcm。getObject(cache,key); if(listnull){ 如果二级缓存不存在该数据,则从数据库中查询 listdelegate。query(ms,parameterObject,rowBounds,resultHandler,key,boundSql); 再将查询到的数据添加到暂存区中 这里主要是添加到TransactionalCacheManager对象(暂存区)中的map中去 tcm。putObject(cache,key,list);issue578and116 }blockquote 由此我们总结出,mybatis的二级缓存在查询时,并不会立刻将数据存入Chache(二级缓存)中。那我们可以思考一下,在什么时候才能将数据存入Cache(二级缓存)中呢。为了解决疑惑,咱们继续进入commit()方法中查看。commit执行分析 这里主要两步操作。 1。delegate。commit清空一级缓存(这里为什么要清空一级缓存呢?) 首先我们知道,一级缓存作用域是sqlsession。在同一个事务的情况下,mybatis执行的所有sql都是同一个sqlsession对象进行操作的。除非创建了一个新的事务。spring中使用Transactional(propagationPropagation。REQUIRESNEW)来创建一个新的事务。那么此时,在新事务中会new一个sqlsession来执行sql。那么如果我们在同一个事务中,执行多条sql时都是同一个sqlsession。只有当sql执行完了之后,并且无异常,commit后,就将一级缓存清除。注意:如果在执行sql出现异常并调用session。rollback时,也会清除缓存。 进入delegate。commit方法中,最终会进入如下方法:Override publicvoidclearLocalCache(){ if(!closed){ 这里的localCache就是一级缓存(PerpetualCache对象) localCache。clear(); localOutputParameterCache。clear(); } } 2。tcm。commit清除二级缓存。 进入tcm。commit中publicvoidcommit(){ 遍历暂存区中的数据 for(TransactionalCachetxCache:transactionalCaches。values()){ txCache。commit(); } } 进入txCache。commit();publicvoidcommit(){ clearOnCommit:true表示执行了更新,修改,添加操作。false表示执行了查询操作。 if(clearOnCommit){ 如果没有执行查询操作,则将二级缓存清空(这里如果实现列自定义的二级缓存,会进入自定义实现类中) delegate:的类型是Cache(二级缓存)。 delegate。clear(); } 将暂存区的数据添加到二级缓存中。 flushPendingEntries(); reset(); } 进入flushPendingEntries();privatevoidflushPendingEntries(){ 遍历拿到暂存区中的数据,一个一个添加到二级缓存中 for(Map。EntryObject,Objectentry:entriesToAddOnCommit。entrySet()){ delegate。putObject(entry。getKey(),entry。getValue()); } for(Objectentry:entriesMissedInCache){ if(!entriesToAddOnCommit。containsKey(entry)){ delegate。putObject(entry,null); } } } 那么flushPendingEntries方法具体执行逻辑为:遍历所有的暂存区的数据,添加到缓存中。 因此证实了我的猜想,二级缓存逻辑。查询会将数据存入暂存区中,只有等到sql执行完成并没有异常。commit之后。才会将数据存入二级缓存中。并且commit时还会判断是不是修改操作。如果是的话,会清空二级缓存。那么我们继续看修改操作。修改执行过程 修改源码跟踪 进入flushCacheIfRequired(ms);如下:privatevoidflushCacheIfRequired(MappedStatementms){ 获取二级缓存中的数据。 Cachecachems。getCache(); if(cache!nullms。isFlushCacheRequired()){ 如果二级缓存不为空,清空暂存区的数据 tcm。clear(cache); } } 进入tcm。clear(cache);如下: 这里看到,是进入了暂存区中的clear方法。因此这里执行的是清空暂存区的数据。咱们继续往下看。Override publicvoidclear(){ 这个属性是之前commit中的那个判断。修改就设置为true clearOnCommittrue; 清空暂存区的数据 entriesToAddOnCommit。clear(); } 回到上面我说的修改源码开头。如下 进入update方法: 然后直接进入clearLocalCache方法。如下:Override publicvoidclearLocalCache(){ if(!closed){ 这里的localCache是PerpetualCache对象。 localCache。clear(); localOutputParameterCache。clear(); } } 在文章开头源码的解释已经了解到。PerpetualCache是一级缓存对象。因此这里主要的逻辑是清空一级缓存。 最后让我们继续回顾一下修改的方法: flushCacheIfRequired:主要的功能是清空二级缓存 delegate。update(ms,parameterObject):主要的逻辑是清空一级缓存。 因此我们结合之前查询,commit,修改来说:不管是查询还是修改,在二级缓存情况下。 查询:只会将数据存入暂存区。 修改:只会将暂存区的数据清除。 commit: 1。判断是否是修改操作。修改就清空二级缓存 2。不是修改。就遍历暂存区的数据,然后一个一个添加到二级缓存中。flushPendingEntries方法在上面的commit中。