Mybatis缓存原理及具体源码剖析(整合别人博客)

it2024-10-27  37

Mybatis缓存原理

一、缓存简介

缓存是MyBatis里比较重要的部分,目的就是提升查询的效率和减少数据库的压力,有两种缓存:

SESSION或STATEMENT作用域级别的缓存,默认是SESSION,BaseExecutor中根据MappedStatement的Id、SQL、参数值以及rowBound(边界)来构造CacheKey,并使用BaseExccutor中的localCache来维护此缓存。

全局的二级缓存,通过CacheExecutor来实现,其委托TransactionalCacheManager来保存/获取缓存。

 

二、Mybatis一级与二级缓存

1、一级缓存

Mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中用一个HashMap来存储数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象是无法访问的。

DefaultSqlSession 里面只有两个属性,Configuration 是全局的,所以缓存只可能放在Executor 里面维护——SimpleExecutor/ReuseExecutor/BatchExecutor 的父类BaseExecutor 的构造函数中持有了PerpetualCache。在同一个会话里面,多次执行相同的SQL 语句,会直接从内存取到缓存的结果,不会再发送SQL 到数据库。但是不同的会话里面,即使执行的SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。

流程:

第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来,key是查询的完成sql语句,里面有参数,不同的参数是不同的key

第二次执行select会从缓存中查询数据,如果select相同并且参数一样,那么就能从缓存汇总返回数据,不用去查数据库了,从而提高效率。

//同Session

SqlSession session1 = sqlSessionFactory.openSession();

BlogMapper mapper1 = session1.getMapper(BlogMapper.class);

System.out.println(mapper1.selectBlogById(1002));

System.out.println(mapper1.selectBlogById(1002));

同一个会话中,update(包括delete)会导致一级缓存被清空

//同Session

SqlSession session1 = sqlSessionFactory.openSession();

BlogMapper mapper1 = session1.getMapper(BlogMapper.class);

System.out.println(mapper1.selectBlogById(1002));

Blog blog3 = new Blog();

blog3.setBid(1002);

blog3.setName("mybatis缓存机制修改");

mapper1.updateBlog(blog3);

session1.commit();// 注意要提交事务,否则不会清除缓存

System.out.println(mapper1.selectBlogById(1002));

 

注意事项:

a、如果SqlSession执行了insert,update,delete并commit了,那么mybatis就会清空当前SqlSession中所有的一级缓存数据,这样可以保证缓存中存的数据永远和数据库一致,避免出现脏读

b、当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis是默认开启一级缓存的,不需要配置

c、当服务器集群的时候,每个sqlSesssion有自己独立的缓存,相互之间不共享,所以每个在服务器集群的时候mybatis的一级缓存会产生数据冲突问题。

d、如何禁止一级缓存:

方案1:在sql语句上随机生成不同的参数 存在缺点:map集合可能爆

方案2:将一级缓存的级别设为 statement 级别的,这样每次查询结束都会清掉一级缓存。

方案 3:用二级缓存。

 

e、spring整合mybatis之后,由spring管理的sqlSeesion在sql方法(增删改查等操作)执行完毕后就自行关闭了sqlSession,不需要我们对其进行手动关闭.

f、spring整合mybatis后,非事务环境下,每次操作数据库都使用新的sqlSession对象。因此mybatis的一级缓存无法使用(一级缓存针对同一个sqlsession有效),由于使用了数据库连接池,默认每次查询完之后自动commit,这就导致两次查询使用的不是同一个sqlSessioin,根据一级缓存的原理,它将永远不会生效。

当我们开启了事务,两次查询都在同一个sqlSession中,从而让第二次查询命中了一级缓存。

 

 

2、二级缓存

二级缓存是mapper级别的缓存,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个缓存区域。

2.1、Mybatis二级缓存的配置

配置 mybatis.configuration.cache-enabled=true,只要没有显式地设置cacheEnabled=false,都会用CachingExecutor 装饰基本的执行器。

Mapper.xml 中配置<cache/>标签:

2.2、springboot项目中使用的配置

在启动类中加上@EnableCaching注解在需要缓存的mapper中加上@CacheNamespace(implementation = MybatisRedisCache.class)这个注解和二级缓存的类就可以了

  二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。如果你的MyBatis使用了二级缓存,并且你的Mapperselect语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存   —> 一级缓存 —> 数据库。

  

  注:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true insert/delete/update 语句时,缓存会获得更新。

  

<select id="selectBlog" resultMap="BaseResultMap" useCache="false">

  二级缓存验证(验证二级缓存需要先开启二级缓存)

事务不提交,二级缓不存在

System.out.println(mapper1.selectBlogById(1002));

// 事务不提交的情况下,二级缓存不会写入 // session1.commit(); System.out.println(mapper2.selectBlogById(1002));

  为什么事务不提交,二级缓存不生效?因为二级缓存使用TransactionalCacheManagerTCM)来管理,最后又调用了TransactionalCache getObject()putObject commit()方法,TransactionalCache里面又持有了真正的Cache 对象,比如是经过层层装饰的PerpetualCache。在putObject 的时候,只是添加到了entriesToAddOnCommit 里面,只有它的commit()方法被调用的时候才会调用flushPendingEntries()真正写入缓存。它就是在DefaultSqlSession 调用commit()的时候被调用的。

 

什么时候开启二级缓存?

一级缓存默认是打开的,二级缓存需要配置才可以开启。那么我们必须思考一个问题,在什么情况下才有必要去开启二级缓存?

因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以适合在查询为主的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义。如果多个namespace 中有针对于同一个表的操作,比如Blog 表,如果在一个namespace 中刷新了缓存,另一个namespace 中没有刷新,就会出现读到脏数据的情况。所以,推荐在一个Mapper 里面只操作单表的情况使用。

  如果要让多个namespace 共享一个二级缓存,应该怎么做?跨namespace 的缓存共享的问题,可以使用<cache-ref>来解决:

<cache-ref namespace="com.wuzz.crud.dao.DepartmentMapper" />

  cache-ref 代表引用别的命名空间的Cache 配置,两个命名空间的操作使用的是同一个Cache。在关联的表比较少,或者按照业务可以对表进行分组的时候可以使用。

  注意:在这种情况下,多个Mapper 的操作都会引起缓存刷新,缓存的意义已经不大了.

第三方缓存做二级缓存

  注意:如果应用是分布式部署,由于二级缓存存储在本地,必然导致查询出脏数据,所以,分布式部署的应用不建议开启。

 

Mybatis缓存源码分析

一、链接Mybatis传统方式

String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); try {   Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101); } finally {   session.close(); }

 

二、一级缓存源码分析

   1SqlSessionFactoryBuilder.java获取SqlSessionFactory。主要是通过解析配置文件

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {     SqlSessionFactory var5;     try {         XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);         var5 = this.build(parser.parse());     } catch (Exception var14) {         throw ExceptionFactory.wrapException("Error building SqlSession.", var14);     } finally {         ErrorContext.instance().reset();         try {             inputStream.close();         } catch (IOException var13) {         }     }     return var5; }

 

   2SqlSessionFactoryBuilder.java通过获取到的config构建SqlSessionFactory对象。

public SqlSessionFactory build(Configuration config) {     return new DefaultSqlSessionFactory(config); }

3DefaultSqlSessionFactory.java获取SqlSession对象。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {     Transaction tx = null;     DefaultSqlSession var8;     try {         Environment environment = this.configuration.getEnvironment();         TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);         tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);         Executor executor = this.configuration.newExecutor(tx, execType);         var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);     } catch (Exception var12) {         this.closeTransaction(tx);         throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);     } finally {         ErrorContext.instance().reset();     }     return var8; }

4BaseExecutor.java,如果没有设置二级缓存根据配置文件选择执行器,默认为SimpleExecutor,实现BaseExecutor.java

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {     executorType = executorType == null ? this.defaultExecutorType : executorType;     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;     Object executor;     if (ExecutorType.BATCH == executorType) {         executor = new BatchExecutor(this, transaction);     } else if (ExecutorType.REUSE == executorType) {         executor = new ReuseExecutor(this, transaction);     } else {         executor = new SimpleExecutor(this, transaction);     }     if (this.cacheEnabled) {         executor = new CachingExecutor((Executor)executor);     }     Executor executor = (Executor)this.interceptorChain.pluginAll(executor);     return executor; }

5、DefaultSqlSession.java在执行selectOne的时候,默认执行selectList,而后返回数据。

public <T> T selectOne(String statement, Object parameter) {     List<T> list = this.selectList(statement, parameter);     if (list.size() == 1) {         return list.get(0);     } else if (list.size() > 1) {         throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());     } else {         return null;     } }

6、DefaultSqlSession.java获取通过Executor执行查询操作。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {     List var5;     try {         MappedStatement ms = this.configuration.getMappedStatement(statement);         var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);     } catch (Exception var9) {         throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);     } finally {         ErrorContext.instance().reset();     }     return var5; }

7、BaseExcutor.java执行具体查询。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {     BoundSql boundSql = ms.getBoundSql(parameter);     CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);     return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql); }

8、BaseExcutor.java自组装一级缓存Key

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {     if (this.closed) {         throw new ExecutorException("Executor was closed.");     } else {         CacheKey cacheKey = new CacheKey();         cacheKey.update(ms.getId());         cacheKey.update(rowBounds.getOffset());         cacheKey.update(rowBounds.getLimit());         cacheKey.update(boundSql.getSql());         List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();         TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();         Iterator var8 = parameterMappings.iterator();         while(var8.hasNext()) {             ParameterMapping parameterMapping = (ParameterMapping)var8.next();             if (parameterMapping.getMode() != ParameterMode.OUT) {                 String propertyName = parameterMapping.getProperty();                 Object value;                 if (boundSql.hasAdditionalParameter(propertyName)) {                     value = boundSql.getAdditionalParameter(propertyName);                 } else if (parameterObject == null) {                     value = null;                 } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {                     value = parameterObject;                 } else {                     MetaObject metaObject = this.configuration.newMetaObject(parameterObject);                     value = metaObject.getValue(propertyName);                 }                 cacheKey.update(value);             }         }         if (this.configuration.getEnvironment() != null) {             cacheKey.update(this.configuration.getEnvironment().getId());         }         return cacheKey;     } }

9、BaseExcutor.java执行查询操作,先从一级缓存中查找,如果没有,再向DB查数据,最后填充到一级缓存中。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());     if (this.closed) {         throw new ExecutorException("Executor was closed.");     } else {         if (this.queryStack == 0 && ms.isFlushCacheRequired()) {             this.clearLocalCache();         }         List list;         try {             ++this.queryStack;             list = resultHandler == null ? (List)this.localCache.getObject(key) : null;             if (list != null) {                 this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);             } else {                 list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);             }         } finally {             --this.queryStack;         }         if (this.queryStack == 0) {             Iterator var8 = this.deferredLoads.iterator();             while(var8.hasNext()) {                 BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();                 deferredLoad.load();             }             this.deferredLoads.clear();             if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {                 this.clearLocalCache();             }         }         return list;     } }

CachingExecutor:(二级缓存开启使用该executor)

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {     Cache cache = ms.getCache();     if (cache != null) {         this.flushCacheIfRequired(ms);         if (ms.isUseCache() && resultHandler == null) {             this.ensureNoOutParams(ms, boundSql);             List<E> list = (List)this.tcm.getObject(cache, key);             if (list == null) {                 list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);                 this.tcm.putObject(cache, key, list);             }             return list;         }     }     return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

Mybatis二级缓存源码剖析

  Mybatis二级缓存开启的地方

     当mybatis的主配置文件的settings中设置cacheEnabled=true时,创建Executor的时候,默认是创建简单的执行器,这里替换成CacheExecutor,是执行器接口的一个实现类

   newExecutor 方法是在sqlSessionFactory.openSession()是调用的

public Executor newExecutor(Transaction transaction) {   return newExecutor(transaction, defaultExecutorType); } public Executor newExecutor(Transaction transaction, ExecutorType executorType) {   executorType = executorType == null ? defaultExecutorType : executorType;   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;   Executor executor;   // 根据executor类型创建对象的Executor对象   if (ExecutorType.BATCH == executorType) {     executor = new BatchExecutor(this, transaction);   } else if (ExecutorType.REUSE == executorType) {     executor = new ReuseExecutor(this, transaction);   } else {     executor = new SimpleExecutor(this, transaction);   }   // 如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰   if (cacheEnabled) {     executor = new CachingExecutor(executor);   }   // 执行拦截器链的拦截逻辑   executor = (Executor) interceptorChain.pluginAll(executor);   return executor; }

 

   代码执行到这里,我们的执行器已经不是默认的simple执行器了,而是CachingExecutor. 后续执行代码的时候,就是通过该执行器执行代码

  Mybatis二级缓存的创建过程

    二级缓存的创建是在解析mapper配置文件的时候创建的。

  流程如下:

    XMLConfigBuilder:解析mybatis主配置文件--->解析mybatis的mapper配置文件---->解析cache标签---->创建缓存对象

public class XMLConfigBuilder extends BaseBuilder {   // 解析主配置文件      public Configuration parse() {     // 防止parse()方法被同一个实例多次调用     if (parsed) {       throw new BuilderException("Each XMLConfigBuilder can only be used once.");     }     parsed = true;     // 调用XPathParser.evalNode()方法,创建表示configuration节点的XNode对象。     // 调用parseConfiguration()方法对XNode进行处理     parseConfiguration(parser.evalNode("/configuration"));     return configuration;   }   // 解析mybatis主配置文件下configuration对象   private void parseConfiguration(XNode root) {     try {       //issue #117 read properties first       propertiesElement(root.evalNode("properties"));       Properties settings = settingsAsProperties(root.evalNode("settings"));       loadCustomVfs(settings);       typeAliasesElement(root.evalNode("typeAliases"));       pluginElement(root.evalNode("plugins"));       objectFactoryElement(root.evalNode("objectFactory"));       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));       reflectorFactoryElement(root.evalNode("reflectorFactory"));       settingsElement(settings);       // read it after objectFactory and objectWrapperFactory issue #631       environmentsElement(root.evalNode("environments"));       databaseIdProviderElement(root.evalNode("databaseIdProvider"));       typeHandlerElement(root.evalNode("typeHandlers"));       mapperElement(root.evalNode("mappers"));     } catch (Exception e) {       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);     }   } // 解析mapper标签  private void mapperElement(XNode parent) throws Exception {     if (parent != null) {       for (XNode child : parent.getChildren()) {         // 通过<package>标签指定包名         if ("package".equals(child.getName())) {           String mapperPackage = child.getStringAttribute("name");           configuration.addMappers(mapperPackage);         } else {           String resource = child.getStringAttribute("resource");           String url = child.getStringAttribute("url");           String mapperClass = child.getStringAttribute("class");           // 通过resource属性指定XML文件路径           if (resource != null && url == null && mapperClass == null) {             ErrorContext.instance().resource(resource);             InputStream inputStream = Resources.getResourceAsStream(resource);             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());             mapperParser.parse();           } else if (resource == null && url != null && mapperClass == null) {             // 通过url属性指定XML文件路径             ErrorContext.instance().resource(url);             InputStream inputStream = Resources.getUrlAsStream(url);<br>       // 解析具体的mapper文件             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());             mapperParser.parse();           } else if (resource == null && url == null && mapperClass != null) {             // 通过class属性指定接口的完全限定名             Class<?> mapperInterface = Resources.classForName(mapperClass);             configuration.addMapper(mapperInterface);           } else {             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");           }         }       }     }   }

// 解析具体的mapper文件

XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());

mapperParser.parse();

// 从这里进去XMLMapperBuilder,解析具体的mapper文件,代码如下:

private void cacheElement(XNode context) throws Exception {   if (context != null) {     String type = context.getStringAttribute("type", "PERPETUAL");     Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);     String eviction = context.getStringAttribute("eviction", "LRU");     Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);     Long flushInterval = context.getLongAttribute("flushInterval");     Integer size = context.getIntAttribute("size");     boolean readWrite = !context.getBooleanAttribute("readOnly", false);     boolean blocking = context.getBooleanAttribute("blocking", false);     Properties props = context.getChildrenAsProperties();<br>    // 通过MapperBuilderAssiatant创建Cache     builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);   } }

 MapperBuilderAssistant

public Cache useNewCache(Class<? extends Cache> typeClass,       Class<? extends Cache> evictionClass,       Long flushInterval,       Integer size,       boolean readWrite,       boolean blocking,       Properties props) {     Cache cache = new CacheBuilder(currentNamespace)         .implementation(valueOrDefault(typeClass, PerpetualCache.class))         .addDecorator(valueOrDefault(evictionClass, LruCache.class))         .clearInterval(flushInterval)         .size(size)         .readWrite(readWrite)         .blocking(blocking)         .properties(props)         .build();     configuration.addCache(cache);     currentCache = cache;  // 这里添加进来了     return cache;   }

如上面的代码所示,在获取<cache>标签的所有属性信息后,调用MapperBuilderAssistant对象的userNewCache()方法创建二级缓存实例,然后通过MapperBuilderAssistant的currentCache属性保存二级 缓存对象的引用。在调用MapperBuilderAssistant对象的addMappedStatement()方法创建MappedStatement对象时会将当前命名空间对应的二级缓存对象的引用添加到MappedStatement对象中。

下面是创建MappedStatement对象的关键代码:

public MappedStatement addMappedStatement(       String id,       SqlSource sqlSource,       StatementType statementType,       SqlCommandType sqlCommandType,       Integer fetchSize,       Integer timeout,       String parameterMap,       Class<?> parameterType,       String resultMap,       Class<?> resultType,       ResultSetType resultSetType,       boolean flushCache,       boolean useCache,       boolean resultOrdered,       KeyGenerator keyGenerator,       String keyProperty,       String keyColumn,       String databaseId,       LanguageDriver lang,       String resultSets) {     if (unresolvedCacheRef) {       throw new IncompleteElementException("Cache-ref not yet resolved");     }     id = applyCurrentNamespace(id, false);     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)         .resource(resource)         .fetchSize(fetchSize)         .timeout(timeout)         .statementType(statementType)         .keyGenerator(keyGenerator)         .keyProperty(keyProperty)         .keyColumn(keyColumn)         .databaseId(databaseId)         .lang(lang)         .resultOrdered(resultOrdered)         .resultSets(resultSets)         .resultMaps(getStatementResultMaps(resultMap, resultType, id))         .resultSetType(resultSetType)         .flushCacheRequired(valueOrDefault(flushCache, !isSelect))         .useCache(valueOrDefault(useCache, isSelect))<br>              .cache(currentCache); // 在这里添加进来了 ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);  if (statementParameterMap != null){ statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }

 2.3 mybaits的二级缓存的原理

 CacheExcutor的query操作源码如下:

public class CachingExecutor implements Executor {   private final Executor delegate;   private final TransactionalCacheManager tcm = new TransactionalCacheManager();   public CachingExecutor(Executor delegate) {     this.delegate = delegate;     delegate.setExecutorWrapper(this);   }     @Override     public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {       BoundSql boundSql = ms.getBoundSql(parameterObject);       // 调用createCacheKey()方法创建缓存Key       CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);       return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);     }     @Override     public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)         throws SQLException {       // 获取MappedStatement对象中维护的二级缓存对象       Cache cache = ms.getCache();       if (cache != null) {         // 判断是否需要刷新二级缓存         flushCacheIfRequired(ms);         if (ms.isUseCache() && resultHandler == null) {           ensureNoOutParams(ms, boundSql);           // 从MappedStatement对象对应的二级缓存中获取数据           @SuppressWarnings("unchecked")           List<E> list = (List<E>) tcm.getObject(cache, key);           if (list == null) {             // 如果缓存数据不存在,则从数据库中查询数据             list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);             // 將数据存放到MappedStatement对象对应的二级缓存中             tcm.putObject(cache, key, list); // issue #578 and #116           }           return list;         }       }       return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);     } }

如上面的代码所示,在CachingExecutor的query()方法中, 首先调用createCacheKey()方法创建缓存Key对象,然后调用MappedStatement对象的getCache()方法获取MappedStatement对象 中维护的二级缓存对象。然后尝试从二级缓存对象中获取结果,如果获取不到,则调用目标Executor对象的query()方法从数据库获取数据,再将数据添加到二级缓存中。

由上代码可知,二级缓存是从TransactionalCacheManager中获取的,该类的源码如下:

public class TransactionalCacheManager {   // 通过HashMap对象维护二级缓存对应的TransactionalCache实例   private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();   public void clear(Cache cache) {     getTransactionalCache(cache).clear();   }   public Object getObject(Cache cache, CacheKey key) {     // 获取二级缓存对应的TransactionalCache对象,然后根据缓存Key获取缓存对象     return getTransactionalCache(cache).getObject(key);   }       public void putObject(Cache cache, CacheKey key, Object value) {     getTransactionalCache(cache).putObject(key, value);   }   public void commit() {     for (TransactionalCache txCache : transactionalCaches.values()) {       txCache.commit();     }   }   public void rollback() {     for (TransactionalCache txCache : transactionalCaches.values()) {       txCache.rollback();     }   }   private TransactionalCache getTransactionalCache(Cache cache) {     // 获取二级缓存对应的TransactionalCache对象     TransactionalCache txCache = transactionalCaches.get(cache);     if (txCache == null) {       // 如果获取不到则创建,然后添加到Map中       txCache = new TransactionalCache(cache);       transactionalCaches.put(cache, txCache);     }     return txCache;   } }

 2.4 mybaits的二级缓存删除地方

 当执行update更新操作的时候,同一命名空间下的二级缓存会被清空,下面是CachingExecutor的update方法

@Override public int update(MappedStatement ms, Object parameterObject) throws SQLException {   // 如果需要刷新,则更新缓存   flushCacheIfRequired(ms);   return delegate.update(ms, parameterObject); } public boolean isFlushCacheRequired() {   return flushCacheRequired; } private void flushCacheIfRequired(MappedStatement ms) {   Cache cache = ms.getCache();   if (cache != null && ms.isFlushCacheRequired()) {          tcm.clear(cache);   } }

 

 

 

 

最新回复(0)