精华内容
下载资源
问答
  • MyBatis的缓存机制

    2019-08-08 20:57:13
    MyBatis的查询缓存分为一级缓存和二级缓存 一级缓存是SqlSession级别的缓存 二级缓存是mapper级别的缓存 二级缓存是多个SqlSession共享的。MyBatis通过缓存机制减轻数据压力,提高数据库性能。 一级缓存 MyBatis的...

    概述
    在实际开发中,通常对数据库查询的性能要求很高,而MyBatis提供了查询缓存来缓存数据,从而达到提供查询性能的要求

    MyBatis的查询缓存分为一级缓存和二级缓存

    一级缓存是SqlSession级别的缓存

    二级缓存是mapper级别的缓存

    二级缓存是多个SqlSession共享的。MyBatis通过缓存机制减轻数据压力,提高数据库性能。
    在这里插入图片描述

    一级缓存
    MyBatis的一级缓存是SqlSession级别的缓存,在操作数据库时需要构造SQLSession对象,在对象中有一个HashMap用于存储缓存数据,不同的SQLSession之间缓存数据区域(HashMap)是互相不影响的

    一级缓存的作用域是SqlSession范围的,当在同一个SQLSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存)中,第二次查询时会从缓存中获取数据,不再去底层进行数据库查询,从而提高了查询效率

    需要注意的是:如果SqlSession执行了DML操作(insert、update、delete),并执行commit()操作,Mybatis则会清空SQLSession中的一级缓存,这样做的目的是未来保存缓存中存储的是最新的信息,避免出现脏读现象。

    当一个SqlSession结束后该SQLSession中的一级缓存也就不存在了,Mybatis默认开启一级缓存,不需要进行任何配置
    注:Mybatis的缓存机制是基于id进行缓存,也就是说Mybatis在使用HashMap缓存数据时,是使用对象的id作为key,而对象作为value保存

    二级缓存
    二级缓存是mapper级别的缓存,使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储,相比一级缓存SqlSession,二级缓存的范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

    二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的SqlSession两次执行相同的namespace的下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率

    Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存

    开启二级缓存

    <settings> 
    <setting name="cacheEnabled" value="true"/> 
    </settings> 
    

    注:cacheEnabled的value为true表示在此配置文件下开启二级缓存
    或者在SQL映射文件下

    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    

    如上就可以修改属性,单开启只需<cache/>
    可用的清除策略有:

    LRU – 最近最少使用:移除最长时间不被使用的对象。
    FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
    默认的清除策略是 LRU。

    flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

    size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

    readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
    注:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
    (以上皆引用自官方文档,有兴趣的小伙伴可以自行查看MyBatis官方文档

    展开全文
  • Mybatis的缓存机制理解

    千次阅读 2019-02-21 17:44:30
    针对查询操作,mybatis支持通过缓存的方式来减少SQL调用,提高查询性能。在缓存级别方面分为一级缓存和二级缓存, 一级缓存的粒度较小,是与某个SqlSession绑定,只对该SqlSession相关查询操作进行缓存,不同...

    概述

    针对查询操作,mybatis支持通过缓存的方式来减少SQL的调用,提高查询性能。在缓存级别方面分为一级缓存和二级缓存,

    1. 一级缓存的粒度较小,是与某个SqlSession绑定的,只对该SqlSession的相关查询操作进行缓存,不同SqlSession实例之间相互不影响,缓存为使用本地内存实现;
    2. 二级缓存是一种全局缓存,是由所有SqlSession实例所共享的,即不同SqlSession实例查询时产生的缓存,对其他SqlSession实例可见。

    一级缓存

    • mybatis的一级缓存支持两种缓存级别,分别是SESSION和STATEMENT,默认的一级缓存级别为SESSION。
    • mybatis的一级缓存是默认开启的。
    • 一级缓存的使用示意图如下:(图片引用自:mybatis一级缓存二级缓存
      在这里插入图片描述

    SESSION级别

    • 对该SqlSession实例发起的查询操作进行缓存,即由同一SqlSession实例发起的多次相同(SQL和SQL的参数值都相同)的查询操作,第一次是查询数据库,后续则查询缓存;但是如果另外一个SqlSession实例进行相同的查询操作,则需要进行数据库查询。
    • 针对更新操作,如果是该SqlSession自身进行了更新操作,则该SqlSession对应的一级缓存会被清空,但是如果是其他SqlSession实例进行了更新操作,则此更新操作对该SqlSession不可见,所以该SqlSession的缓存数据是过期失效数据,所以SqlSession实例的生命周期不能过长,否则可能出现数据不一致现象。

    STATEMENT级别

    • 该级别是指缓存只针对当前执行的查询语句有效,故每次语句执行完之后都会清空缓存,其实是相当于没有缓存,即该sqlSession实例下次调用相同的SQL语句和相同参数值时,由于上一次语句执行后,缓存被清空了,故需要继续查询数据库。具体可以看源码的query实现:

      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 (closed) {
            throw new ExecutorException("Executor was closed.");
          }
          if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
          }
          List<E> list;
          try {
            queryStack++;
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
              // 从缓存获取指定SQL的结果,而不用去数据库查询
              handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
              // 去数据库查询
              list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
          } finally {
            queryStack--;
          }
      
          // 由于SqlSession不是线程安全的,故任何时候只存在一个线程操作,故queryStack总是0的
          if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
              deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
      
            // 如果是STATEMENT,则每次执行完查询都清空缓存,故其实是没有缓存
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
              // issue #482
              clearLocalCache();
            }
          }
          return list;
      }
      

    配置方法

    • mybatis的一级缓存是内部实现的一个特性,用户不能配置,默认情况下为开启的。同时内部也是使用一个基于HashMap实现的本地内存来实现,故在配置方面只能配置缓存级别为STATEMENT来关闭一级缓存。配置主要是在全局配置mybatisConfig.xml中配置,如下:

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
          <settings>
              <setting name="localCacheScope" value="STATEMENT"/>
              
              ...
              
          </settings>
          
          ...
      
      </configuration>
      

    二级缓存

    • mybatis默认没有开启二级缓存,二级缓存支持在配置中自定义底层所用的缓存实现,包括使用本地内存和分布式缓存。

    • 二级缓存是基于namespace的,即作用域为mapper,故需要在每个mapper中配置自身所使用的二级缓存实现以及缓存策略。同时由于二级缓存是基于namespace的,所以不同namespace之间的相互不影响的,如一个namespace使用的本地内存,另外一个namespace使用的是分布式缓存,则如果不同namespace对同一张数据表的数据进行了操作,则可能会存在数据不一致问题。

    • 如果二级缓存使用本地内存的话,则由于开启二级缓存之后,需要在本地内存缓存大量的数据,即对所有SqlSession实例的查询进行缓存,故可能造成内存资源的开销较大。

    • 二级缓存的使用示意图如下:(图片引用自:mybatis一级缓存二级缓存
      在这里插入图片描述

    配置方法

    • 二级缓存的配置分为三步:
      1. 首先在mybatisConfig.xml文件中配置全局开关的:

        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-config.dtd">
        <configuration>
            <settings>
                <setting name="cacheEnabled" value="true" />
                
                ...
                
            </settings>
            
            ...
        
        </configuration>
        
      2. 然后需要在各个mapper对应的配置文件mapper.xml中配置cache标签,可以指定该mapper使用的二级缓存的底层实现和相关缓存配置等。cache标签也可以是空标签,则使用默认的基于本地内存的二级缓存实现。

        <mapper namespace="dao.userdao">
           
            <!-- Cache 配置 -->
            <!-- <cache /> -->
            <!-- <cache
                eviction="FIFO"
                flushInterval="60000"
                size="512"
                readOnly="true" /> -->
            <cache type="org.mybatis.caches.ehcache.EhcacheCache" />
        </mapper>
        
      3. 这步是可选的,即在mapper内部的每个select可以通过useCache开关来控制当前查询select是否使用二级缓存:默认为true。

        <select id="selectBlog"
                resultMap="BaseResultMap" 
                parameterType="java.lang.Long"
                useCache="false">
            ...
            
        </select>
        

    总结

    • 由以上分析可知,虽然一级和二级缓存的使用可以减少数据库查询操作,但是都存在造成数据不一致的情况存在:对于一级缓存由于不同sqlSession实例之间相互隔离,则可能出现其中一个更新了数据库数据,但是另外一个由于使用了自身内部的缓存,故读取到失效的旧数据;对于二级缓存,由所有sqlSession实例共享,基于namespace隔离,故如果不同namespace定义了同时操作一个表的SQL语句,则会造成不同namespace之间的缓存不一致问题。所以如果对于mybatis的内部运作机制不理解,可能会由于这些造成数据不一致的情况存在,则可能会导致莫名其妙的问题。
    • 针对以上这些问题,建议统一使用额外的缓存实现,即在应用代码中自定义缓存实现,关闭mybatis的一级和二级缓存,只使用mybatis基于SQL来进行数据库操作。
    展开全文
  • mybatis的缓存机制

    2016-07-14 15:31:55
    正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持;一级缓存基于 PerpetualCache  HashMap 本地缓存,其存储作用域 为 Session,当 Session flush 或 close 之后,该Session中所有 ...
    缓存概述 
    • 正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持;
    • 一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
    • 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。
    • 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
    • MyBatis 的缓存采用了delegate机制 及 装饰器模式设计,当put、get、remove时,其中会经过多层 delegate cache 处理,其Cache类别有:BaseCache(基础缓存)、EvictionCache(排除算法缓存) 、DecoratorCache(装饰器缓存):          BaseCache         :为缓存数据最终存储的处理类,默认为 PerpetualCache,基于Map存储;可自定义存储处理,如基于EhCache、Memcached等; 
                EvictionCache    :当缓存数量达到一定大小后,将通过算法对缓存数据进行清除。默认采用 Lru 算法(LruCache),提供有 fifo 算法(FifoCache)等; 
                DecoratorCache:缓存put/get处理前后的装饰器,如使用 LoggingCache 输出缓存命中日志信息、使用 SerializedCache 对 Cache的数据 put或get 进行序列化及反序列化处理、当设置flushInterval(默认1/h)后,则使用 ScheduledCache 对缓存数据进行定时刷新等。
    • 一般缓存框架的数据结构基本上都是 Key-Value 方式存储,MyBatis 对于其 Key 的生成采取规则为:[hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams]。
    • 对于并发 Read/Write 时缓存数据的同步问题,MyBatis 默认基于 JDK/concurrent中的ReadWriteLock,使用ReentrantReadWriteLock 的实现,从而通过 Lock 机制防止在并发 Write Cache 过程中线程安全问题。

    源码剖解 
    接下来将结合 MyBatis 序列图进行源码分析。在分析其Cache前,先看看其整个处理过程。 
    执行过程: 

    ① 通常情况下,我们需要在 Service 层调用 Mapper Interface 中的方法实现对数据库的操作,上述根据产品 ID 获取 Product 对象。 
    ② 当调用 ProductMapper 时中的方法时,其实这里所调用的是 MapperProxy 中的方法,并且 MapperProxy已经将将所有方法拦截,其具体原理及分析,参考 MyBatis+Spring基于接口编程的原理分析,其 invoke 方法代码为:
    Java代码  收藏代码
    1. //当调用 Mapper 所有的方法时,将都交由Proxy 中的 invoke 处理:  
    2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    3.     try {  
    4.       if (!OBJECT_METHODS.contains(method.getName())) {  
    5.         final Class declaringInterface = findDeclaringInterface(proxy, method);  
    6.         // 最终交由 MapperMethod 类处理数据库操作,初始化 MapperMethod 对象  
    7.         final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);  
    8.         // 执行 mapper method,返回执行结果   
    9.         final Object result = mapperMethod.execute(args);  
    10.         ....  
    11.         return result;  
    12.       }  
    13.     } catch (SQLException e) {  
    14.       e.printStackTrace();  
    15.     }  
    16.     return null;  
    17.   }  

    ③其中的 mapperMethod 中的 execute  方法代码如下: 
    Java代码  收藏代码
    1. public Object execute(Object[] args) throws SQLException {  
    2.     Object result;  
    3.     // 根据不同的操作类别,调用 DefaultSqlSession 中的执行处理  
    4.     if (SqlCommandType.INSERT == type) {  
    5.       Object param = getParam(args);  
    6.       result = sqlSession.insert(commandName, param);  
    7.     } else if (SqlCommandType.UPDATE == type) {  
    8.       Object param = getParam(args);  
    9.       result = sqlSession.update(commandName, param);  
    10.     } else if (SqlCommandType.DELETE == type) {  
    11.       Object param = getParam(args);  
    12.       result = sqlSession.delete(commandName, param);  
    13.     } else if (SqlCommandType.SELECT == type) {  
    14.       if (returnsList) {  
    15.         result = executeForList(args);  
    16.       } else {  
    17.         Object param = getParam(args);  
    18.         result = sqlSession.selectOne(commandName, param);  
    19.       }  
    20.     } else {  
    21.       throw new BindingException("Unkown execution method for: " + commandName);  
    22.     }  
    23.     return result;  
    24.   }  
    由于这里是根据 ID 进行查询,所以最终调用为 sqlSession.selectOne函数。也就是接下来的的 DefaultSqlSession.selectOne 执行; 
    ④ ⑤ 可以在 DefaultSqlSession 看到,其 selectOne 调用了 selectList 方法:
    Java代码  收藏代码
    1. public Object selectOne(String statement, Object parameter) {  
    2.     List list = selectList(statement, parameter);  
    3.     if (list.size() == 1) {  
    4.       return list.get(0);  
    5.     }   
    6.     ...  
    7. }  
    8.   
    9. public List selectList(String statement, Object parameter, RowBounds rowBounds) {  
    10.     try {  
    11.       MappedStatement ms = configuration.getMappedStatement(statement);  
    12.       // 如果启动用了Cache 才调用 CachingExecutor.query,反之则使用 BaseExcutor.query 进行数据库查询   
    13.       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
    14.     } catch (Exception e) {  
    15.       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
    16.     } finally {  
    17.       ErrorContext.instance().reset();  
    18.     }  
    19. }  
    ⑥到这里,已经执行到具体数据查询的流程,在分析 CachingExcutor.query 前,先看看 MyBatis 中 Executor 的结构及构建过程。 


    执行器(Executor): 
    Executor:  执行器接口。也是最终执行数据获取及更新的实例。其类结构如下: 
     
    BaseExecutor: 基础执行器抽象类。实现一些通用方法,如createCacheKey 之类。并且采用 模板模式 将具体的数据库操作逻辑(doUpdate、doQuery)交由子类实现。另外,可以看到变量 localCache: PerpetualCache,在该类采用 PerpetualCache 实现基于 Map 存储的一级缓存,其 query 方法如下:
    Java代码  收藏代码
    1. public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
    2.     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());  
    3.     // 执行器已关闭  
    4.     if (closed) throw new ExecutorException("Executor was closed.");  
    5.     List list;  
    6.     try {  
    7.       queryStack++;   
    8.       // 创建缓存Key  
    9.       CacheKey key = createCacheKey(ms, parameter, rowBounds);   
    10.       // 从本地缓存在中获取该 key 所对应 的结果集  
    11.       final List cachedList = (List) localCache.getObject(key);   
    12.       // 在缓存中找到数据  
    13.       if (cachedList != null) {   
    14.         list = cachedList;  
    15.       } else { // 未从本地缓存中找到数据,开始调用数据库查询  
    16.         //为该 key 添加一个占位标记  
    17.         localCache.putObject(key, EXECUTION_PLACEHOLDER);   
    18.         try {  
    19.           // 执行子类所实现的数据库查询 操作  
    20.           list = doQuery(ms, parameter, rowBounds, resultHandler);   
    21.         } finally {  
    22.           // 删除该 key 的占位标记  
    23.           localCache.removeObject(key);  
    24.         }  
    25.         // 将db中的数据添加至本地缓存中  
    26.         localCache.putObject(key, list);  
    27.       }  
    28.     } finally {  
    29.       queryStack--;  
    30.     }  
    31.     // 刷新当前队列中的所有 DeferredLoad实例,更新 MateObject  
    32.     if (queryStack == 0) {   
    33.       for (DeferredLoad deferredLoad : deferredLoads) {  
    34.         deferredLoad.load();  
    35.       }  
    36.     }  
    37.     return list;  
    38.   }  
    BatchExcutorReuseExcutor SimpleExcutor: 这几个就没什么好说的了,继承了 BaseExcutor 的实现其 doQuery、doUpdate 等方法,同样都是采用 JDBC 对数据库进行操作;三者区别在于,批量执行、重用 Statement 执行、普通方式执行。具体应用及场景在Mybatis 的文档上都有详细说明。 

    CachingExecutor: 二级缓存执行器。个人觉得这里设计的不错,灵活地使用 delegate机制。其委托执行的类是 BaseExcutor。 当无法从二级缓存获取数据时,同样需要从 DB 中进行查询,于是在这里可以直接委托给 BaseExcutor 进行查询。其大概流程为: 

    流程为: 从二级缓存中进行查询 -> [如果缓存中没有,委托给 BaseExecutor] -> 进入一级缓存中查询 -> [如果也没有] -> 则执行 JDBC 查询,其 query 代码如下:
    Java代码  收藏代码
    1. public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
    2.     if (ms != null) {  
    3.       // 获取二级缓存实例  
    4.       Cache cache = ms.getCache();  
    5.       if (cache != null) {  
    6.         flushCacheIfRequired(ms);  
    7.         // 获取 读锁( Read锁可由多个Read线程同时保持)  
    8.         cache.getReadWriteLock().readLock().lock();  
    9.         try {  
    10.           // 当前 Statement 是否启用了二级缓存  
    11.           if (ms.isUseCache()) {  
    12.             // 将创建 cache key 委托给 BaseExecutor 创建  
    13.             CacheKey key = createCacheKey(ms, parameterObject, rowBounds);  
    14.             final List cachedList = (List) cache.getObject(key);  
    15.             // 从二级缓存中找到缓存数据  
    16.             if (cachedList != null) {  
    17.               return cachedList;  
    18.             } else {  
    19.               // 未找到缓存,很委托给 BaseExecutor 执行查询  
    20.               List list = delegate.query(ms, parameterObject, rowBounds, resultHandler);  
    21.               tcm.putObject(cache, key, list);  
    22.               return list;  
    23.             }  
    24.           } else { // 没有启动用二级缓存,直接委托给 BaseExecutor 执行查询   
    25.             return delegate.query(ms, parameterObject, rowBounds, resultHandler);  
    26.           }  
    27.         } finally {  
    28.           // 当前线程释放 Read 锁  
    29.           cache.getReadWriteLock().readLock().unlock();  
    30.         }  
    31.       }  
    32.     }  
    33.     return delegate.query(ms, parameterObject, rowBounds, resultHandler);  
    34. }  
    至此,已经完完了整个缓存执行器的整个流程分析,接下来是对缓存的 缓存数据管理实例进行分析,也就是其 Cache 接口,用于对缓存数据 put 、get及remove的实例对象。 


    Cache 委托链构建: 
    正如最开始的缓存概述所描述道,其缓存类的设计采用 装饰模式,基于委托的调用机制。 
    缓存实例构建: 
    缓存实例的构建 ,Mybatis 在解析其 Mapper 配置文件时就已经将该实现初始化,在 org.apache.ibatis.builder.xml.XMLMapperBuilder 类中可以看到: 
    Java代码  收藏代码
    1. private void cacheElement(XNode context) throws Exception {  
    2.     if (context != null) {  
    3.       // 基础缓存类型  
    4.       String type = context.getStringAttribute("type""PERPETUAL");  
    5.       Class typeClass = typeAliasRegistry.resolveAlias(type);  
    6.       // 排除算法缓存类型  
    7.       String eviction = context.getStringAttribute("eviction""LRU");  
    8.       Class evictionClass = typeAliasRegistry.resolveAlias(eviction);  
    9.       // 缓存自动刷新时间  
    10.       Long flushInterval = context.getLongAttribute("flushInterval");  
    11.       // 缓存存储实例引用的大小  
    12.       Integer size = context.getIntAttribute("size");  
    13.       // 是否是只读缓存  
    14.       boolean readWrite = !context.getBooleanAttribute("readOnly"false);  
    15.       Properties props = context.getChildrenAsProperties();  
    16.       // 初始化缓存实现  
    17.       builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);  
    18.     }  
    19.   }  
    以下是  useNewCache 方法实现: 
    Java代码  收藏代码
    1. public Cache useNewCache(Class typeClass,  
    2.                            Class evictionClass,  
    3.                            Long flushInterval,  
    4.                            Integer size,  
    5.                            boolean readWrite,  
    6.                            Properties props) {  
    7.     typeClass = valueOrDefault(typeClass, PerpetualCache.class);  
    8.     evictionClass = valueOrDefault(evictionClass, LruCache.class);  
    9.     // 这里构建 Cache 实例采用 Builder 模式,每一个 Namespace 生成一个  Cache 实例  
    10.     Cache cache = new CacheBuilder(currentNamespace)  
    11.         // Builder 前设置一些从XML中解析过来的参数  
    12.         .implementation(typeClass)  
    13.         .addDecorator(evictionClass)  
    14.         .clearInterval(flushInterval)  
    15.         .size(size)  
    16.         .readWrite(readWrite)  
    17.         .properties(props)  
    18.         // 再看下面的 build 方法实现  
    19.         .build();  
    20.     configuration.addCache(cache);  
    21.     currentCache = cache;  
    22.     return cache;  
    23. }  
    24.   
    25. public Cache build() {  
    26.     setDefaultImplementations();  
    27.     // 创建基础缓存实例  
    28.     Cache cache = newBaseCacheInstance(implementation, id);  
    29.     setCacheProperties(cache);  
    30.     // 缓存排除算法初始化,并将其委托至基础缓存中  
    31.     for (Class<? extends Cache> decorator : decorators) {  
    32.       cache = newCacheDecoratorInstance(decorator, cache);  
    33.       setCacheProperties(cache);  
    34.     }  
    35.     // 标准装饰器缓存设置,如LoggingCache之类,同样将其委托至基础缓存中  
    36.     cache = setStandardDecorators(cache);  
    37.     // 返回最终缓存的责任链对象  
    38.     return cache;  
    39. }  
    最终生成后的缓存实例对象结构: 
     
    可见,所有构建的缓存实例已经通过责任链方式将其串连在一起,各 Cache 各负其责、依次调用,直到缓存数据被 Put 至 基础缓存实例中存储。 


    Cache 实例解剖: 
    实例类:SynchronizedCache 
    说   明:用于控制 ReadWriteLock,避免并发时所产生的线程安全问题。 
    解   剖: 
    对于 Lock 机制来说,其分为 Read 和 Write 锁,其 Read 锁允许多个线程同时持有,而 Write 锁,一次能被一个线程持有,如果当 Write 锁没有释放,其它需要 Write 的线程只能等待其释放才能去持有。 
    其代码实现:
    Java代码  收藏代码
    1. public void putObject(Object key, Object object) {  
    2.     acquireWriteLock();  // 获取 Write 锁  
    3.     try {  
    4.       delegate.putObject(key, object); // 委托给下一个 Cache 执行 put 操作  
    5.     } finally {  
    6.       releaseWriteLock(); // 释放 Write 锁  
    7.     }  
    8.   }  
    对于 Read 数据来说,也是如此,不同的是 Read 锁允许多线程同时持有 : 
    Java代码  收藏代码
    1. public Object getObject(Object key) {  
    2.     acquireReadLock();  
    3.     try {  
    4.       return delegate.getObject(key);  
    5.     } finally {  
    6.       releaseReadLock();  
    7.     }  
    8.   }  
    其具体原理可以看看 jdk concurrent 中的 ReadWriteLock 实现。 


    实例类:LoggingCache 
    说   明:用于日志记录处理,主要输出缓存命中率信息。 
    解   剖: 
    说到缓存命中信息的统计,只有在 get 的时候才需要统计命中率: 
    Java代码  收藏代码
    1. public Object getObject(Object key) {  
    2.     requests++; // 每调用一次该方法,则获取次数+1  
    3.     final Object value = delegate.getObject(key);  
    4.     if (value != null) {  // 命中! 命中+1  
    5.       hits++;  
    6.     }  
    7.     if (log.isDebugEnabled()) {  
    8.       // 输出命中率。计算方法为: hits / requets 则为命中率  
    9.       log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());  
    10.     }  
    11.     return value;  
    12. }  



    实例类:SerializedCache 
    说   明:向缓存中 put 或 get 数据时的序列化及反序列化处理。 
    解   剖: 
    序列化在Java里面已经是最基础的东西了,这里也没有什么特殊之处: 
    Java代码  收藏代码
    1. public void putObject(Object key, Object object) {  
    2.      // PO 类需要实现 Serializable 接口  
    3.     if (object == null || object instanceof Serializable) {  
    4.       delegate.putObject(key, serialize((Serializable) object));   
    5.     } else {  
    6.       throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);  
    7.     }  
    8.   }  
    9.   
    10.   public Object getObject(Object key) {  
    11.     Object object = delegate.getObject(key);  
    12.     // 获取数据时对 二进制数据进行反序列化  
    13.     return object == null ? null : deserialize((byte[]) object);  
    14.   }  
    其 serialize 及 deserialize 代码就不必要贴了。 


    实例类:LruCache 
    说   明:最近最少使用的:移除最长时间不被使用的对象,基于LRU算法。 
    解   剖: 
    这里的 LRU 算法基于 LinkedHashMap 覆盖其 removeEldestEntry 方法实现。好象之前看过 XMemcached 的 LRU 算法也是这样实现的。 
    初始化 LinkedHashMap,默认为大小为 1024 个元素: 
    Java代码  收藏代码
    1. public LruCache(Cache delegate) {  
    2.     this.delegate = delegate;  
    3.     setSize(1024); // 设置 map 默认大小  
    4. }  
    5. public void setSize(final int size) {  
    6.     // 设置其 capacity 为size, 其 factor 为.75F  
    7.     keyMap = new LinkedHashMap(size, .75F, true) {  
    8.       // 覆盖该方法,当每次往该map 中put 时数据时,如该方法返回 True,便移除该map中使用最少的Entry  
    9.       // 其参数  eldest 为当前最老的  Entry  
    10.       protected boolean removeEldestEntry(Map.Entry eldest) {  
    11.         boolean tooBig = size() > size;  
    12.         if (tooBig) {  
    13.           eldestKey = eldest.getKey(); //记录当前最老的缓存数据的 Key 值,因为要委托给下一个 Cache 实现删除  
    14.         }  
    15.         return tooBig;  
    16.       }  
    17.     };  
    18.   }  
    19.   
    20. public void putObject(Object key, Object value) {  
    21.     delegate.putObject(key, value);  
    22.     cycleKeyList(key);  // 每次 put 后,调用移除最老的 key  
    23. }  
    24. // 看看当前实现是否有 eldestKey, 有的话就调用 removeObject ,将该key从cache中移除  
    25. private void cycleKeyList(Object key) {  
    26.     keyMap.put(key, key); // 存储当前 put 到cache中的 key 值  
    27.     if (eldestKey != null) {  
    28.       delegate.removeObject(eldestKey);  
    29.       eldestKey = null;  
    30.     }  
    31.   }  
    32.   
    33. public Object getObject(Object key) {  
    34.     keyMap.get(key); // 便于 该 Map 统计 get该key的次数  
    35.     return delegate.getObject(key);  
    36.   }  


    实例类:PerpetualCache 
    说   明:这个比较简单,直接通过一个 HashMap 来存储缓存数据。所以没什么说的,直接看下面的 MemcachedCache 吧。 


    自定义二级缓存/Memcached 
    其自定义二级缓存也较为简单,它本身默认提供了对 Ehcache 及 Hazelcast 的缓存支持:Mybatis-Cache,我这里参考它们的实现,自定义了针对 Memcached 的缓存支持,其代码如下: 
    Java代码  收藏代码
    1. package com.xx.core.plugin.mybatis;  
    2.   
    3. import java.util.LinkedList;  
    4. import java.util.concurrent.locks.ReadWriteLock;  
    5. import java.util.concurrent.locks.ReentrantReadWriteLock;  
    6.   
    7. import org.apache.ibatis.cache.Cache;  
    8. import org.slf4j.Logger;  
    9. import org.slf4j.LoggerFactory;  
    10.   
    11. import com.xx.core.memcached.JMemcachedClientAdapter;  
    12. import com.xx.core.memcached.service.CacheService;  
    13. import com.xx.core.memcached.service.MemcachedService;  
    14.   
    15. /** 
    16.  * Cache adapter for Memcached. 
    17.  *  
    18.  * @author denger 
    19.  */  
    20. public class MemcachedCache implements Cache {  
    21.   
    22.     // Sf4j logger reference  
    23.     private static Logger logger = LoggerFactory.getLogger(MemcachedCache.class);  
    24.   
    25.     /** The cache service reference. */  
    26.     protected static final CacheService CACHE_SERVICE = createMemcachedService();  
    27.   
    28.     /** The ReadWriteLock. */  
    29.     private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();  
    30.   
    31.     private String id;  
    32.     private LinkedList<String> cacheKeys = new LinkedList<String>();  
    33.   
    34.     public MemcachedCache(String id) {  
    35.         this.id = id;  
    36.     }  
    37.     // 创建缓存服务类,基于java-memcached-client  
    38.     protected static CacheService createMemcachedService() {  
    39.         JMemcachedClientAdapter memcachedAdapter;  
    40.   
    41.         try {  
    42.             memcachedAdapter = new JMemcachedClientAdapter();  
    43.         } catch (Exception e) {  
    44.             String msg = "Initial the JMmemcachedClientAdapter Error.";  
    45.             logger.error(msg, e);  
    46.             throw new RuntimeException(msg);  
    47.         }  
    48.         return new MemcachedService(memcachedAdapter);  
    49.     }  
    50.   
    51.     @Override  
    52.     public String getId() {  
    53.         return this.id;  
    54.     }  
    55.   
    56.     // 根据 key 从缓存中获取数据  
    57.     @Override  
    58.     public Object getObject(Object key) {  
    59.         String cacheKey = String.valueOf(key.hashCode());  
    60.         Object value = CACHE_SERVICE.get(cacheKey);  
    61.         if (!cacheKeys.contains(cacheKey)){  
    62.             cacheKeys.add(cacheKey);  
    63.         }  
    64.         return value;  
    65.     }  
    66.   
    67.     @Override  
    68.     public ReadWriteLock getReadWriteLock() {  
    69.         return this.readWriteLock;  
    70.     }  
    71.   
    72.     // 设置数据至缓存中  
    73.     @Override  
    74.     public void putObject(Object key, Object value) {  
    75.         String cacheKey = String.valueOf(key.hashCode());  
    76.   
    77.         if (!cacheKeys.contains(cacheKey)){  
    78.             cacheKeys.add(cacheKey);  
    79.         }  
    80.         CACHE_SERVICE.put(cacheKey, value);  
    81.     }  
    82.     // 从缓存中删除指定 key 数据  
    83.     @Override  
    84.     public Object removeObject(Object key) {  
    85.         String cacheKey = String.valueOf(key.hashCode());  
    86.   
    87.         cacheKeys.remove(cacheKey);  
    88.         return CACHE_SERVICE.delete(cacheKey);  
    89.     }  
    90.     //清空当前 Cache 实例中的所有缓存数据  
    91.     @Override  
    92.     public void clear() {  
    93.         for (int i = 0; i < cacheKeys.size(); i++){  
    94.             String cacheKey = cacheKeys.get(i);  
    95.             CACHE_SERVICE.delete(cacheKey);  
    96.         }  
    97.         cacheKeys.clear();  
    98.     }  
    99.   
    100.     @Override  
    101.     public int getSize() {  
    102.         return cacheKeys.size();  
    103.     }  
    104. }  

    在  ProductMapper 中增加配置: 
    Xml代码  收藏代码
    1. <cache eviction="LRU" type="com.xx.core.plugin.mybatis.MemcachedCache" />  

    启动Memcached: 
    Shell代码  收藏代码
    1. memcached -c 2000 -p 11211 -vv -U 0 -l 192.168.1.2 -v  

    执行Mapper 中的查询、修改等操作,Test: 
    Java代码  收藏代码
    1. @Test  
    2.     public void testSelectById() {  
    3.         Long pid = 100L;  
    4.   
    5.         Product dbProduct = productMapper.selectByID(pid);  
    6.         Assert.assertNotNull(dbProduct);  
    7.   
    8.         Product cacheProduct = productMapper.selectByID(pid);  
    9.         Assert.assertNotNull(cacheProduct);  
    10.   
    11.         productMapper.updateName("IPad", pid);  
    12.   
    13.         Product product = productMapper.selectByID(pid);  
    14.         Assert.assertEquals(product.getName(), "IPad");  
    15.     }  

    Memcached Loging: 
     
    看上去没什么问题~ OK了。
    展开全文
  • Mybatis - 缓存

    2020-03-15 16:27:09
    1. 概述 什么是缓存缓存就是存在于内存中的临时数据 为什么要使用缓存? 为了减少和数据库交互的次数,提高执行效率 适用于缓存的数据 ...2. Mybatis的一级缓存 Mybatis 默认就是使用一次缓存的,不需要...

    1. 概述

    什么是缓存?
    缓存就是存在于内存中的临时数据

    为什么要使用缓存?
    为了减少和数据库交互的次数,提高执行效率

    适用于缓存的数据
    经常查询并且不经常改变的数据
    数据的正确与否对最终结果影响不大的

    不适用于缓存的数据
    经常改变的数据。
    数据的正确与否对最终结果影响很大的。例如:商品的库存、银行的汇率、股市的牌价等

    2. Mybatis的一级缓存

    Mybatis 默认就是使用一次缓存的,不需要配置

    一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就会存在。当调用 SqlSession 的修改、添加、删除、commit()、close()、clearCache() 等方法时,就会清空一级缓存

    在这里插入图片描述

    • 第一次发起查询用户 id 为 1 的用户信息,Mybatis 会先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息

    • 得到用户信息,将用户信息存储到一级缓存中

    • 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),那么 Mybatis 就会清空SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读

    • 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息

    3. Mybatis的二级缓存

    是Mybatis的SqlSessionFactory的缓存,由同一个SqlSessionFactory创建的SqlSession共享缓存

    二级缓存中存的是散装数据而不是对象,所有第二次查询的时候虽然没有再去查询,但是封装成了另一个对象返回

    当我们使用二级缓存的时候,所缓存的类一定要实现 java.io.Serializable 接口,这样才可以使用序列化的方式来保存对象。
    由于是序列化保存对象,所以二级缓存中存放的是数据,而不是整个对象

    配置方法
    首先在 Mybatis 配置文件中添加配置

    <settings>
        <!-- 开启缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings> 
    
    

    接着在映射文件中配置

    <mapper namespace="com.minifull.mapper.UserMapper">
        <!-- 使用缓存 -->
        <cache/>
    </mapper>
    
    

    最后在需要使用二级缓存的操作上配置 (如果针对每次查询都需要最新数据的操作,要设置成 useCache=“false”,禁用二级缓存

    <select id="listAllUsers" resultMap="UserWithAccountsMap" useCache="true">
        SELECT * FROM user
    </select>
    
    展开全文
  • 关于MyBatis的缓存机制

    2015-09-07 15:10:46
    正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持;一级缓存基于 PerpetualCache HashMap 本地缓存,其存储作用域 为 Session,当 Session flush 或 close 之后,该Session中
  • Mybatis 缓存分析

    2018-05-27 21:38:00
    其实本来不想专门的写一篇关于mybatis缓存的博客的。在之前的博客中已经大致的把mybatis的整体流程讲了一遍。只要按照步骤一步步的点进去,关于缓存的代码很容易就能发现。...mybatis的缓存分为一...
  • MyBatis缓存MaBatis缓存概述什么是缓存为什么使用缓存什么样数据适合使用缓存MyBatis一级缓存和二级缓存一级缓存 MaBatis缓存概述 什么是缓存 存在内存中临时数据 为什么使用缓存 减少和数据库交互次数,...
  • 文章目录1、Mybatis 缓存机制概述2、一级缓存工作原理2.1、证明一级缓存的存在2.1.1、编写用户持久层 Dao 接口2.1.2 编写用户持久层映射文件2.1.3、编写测试方法2.2、一级缓存分析2.3、测试一级缓存的清空3、二级...
  • Mybatis-缓存机制

    2021-02-01 20:14:49
    12.1.1 缓存概述mybatis从数据库中查询数据,如果有多个用户使用同一个SQL语句来查询记录,得到相同查询结果。 如果表中记录很多,查询速度比较慢。使用缓存的目的就是为了提升查询速度。缓存是内存中一个...
  • mybatis缓存概述

    2020-04-02 09:04:22
    1问题引入例如到了发工资那天,员工通过各种形式和条件在企业工资管理系统中查询自己工资...2什么是缓存缓存是一种以空间换时间策略技术,它位于内存中,而数据库位于硬盘中,原理是将数据库中某部份常被使...
  • MyBatis缓存机制

    2020-05-09 19:19:14
    如果缓存的命中率比较低,就没有使用缓存的必要。因此关键在于存储内容访问命中率. 比较适用于:经常查询但是不经常改变,数据正确与否对最终结果影响不大时。 不适用于:经常改变数据,数据正确...
  • Mybatis缓存

    2020-06-21 16:44:50
    一级缓存是SqlSession级别的缓存,只要SqlSession没有flush或close,它就存在。 第一步:编写UserMapper接口 User queryUserById(Integer id); 第二步:编写UserMapper.xml映射文件 <select id="queryUserB
  • MyBatis-22MyBatis缓存配置【一级缓存

    千次阅读 2018-05-03 13:13:19
    概述 一级缓存 概述 ... 使用缓存可以使应用更快的获取数据,避免频繁的...一般提到MyBatis的缓存,都是指二级缓存。 一级缓存(也叫本地缓存)默认会启用,并且不能控制,因此很少提到, 这里仅仅是介绍下一级...
  • mybatis 缓存

    2020-02-04 11:57:03
    一、mybatis缓存概述 什么是缓存 存在于内存中临时数据。 为什么使用缓存 减少和数据库交互次数,提高执行效率。 什么样数据能使用缓存,什么样数据不能使用 适用于缓存: 经常查询并且不经常改变...
  • 概述 一般提到MyBatis缓存的时候...后面介绍MyBatis二级缓存,包括二级缓存的基本配置用法,还有一些常用缓存框架和缓存数据库结合。除此之外还会介绍二级缓存的适用场景,以及如何避免产生脏数据。 一、Myba...
  • mybatis缓存概述CacheKey: hashcode(哈希值),checksum,namespace+id(namespace为mapper文件命名空间,id为sqlid),.sql(查询sql), parameter(查询sql参数); 以上字段,用于从缓存中查询值
  • Mybatis缓存简介

    2018-06-16 13:11:36
    1.Mybatis数据库缓存概述 Mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。Mybatis提供一级缓存和二级缓存Mybatis提供一级缓存和二级缓存Mybatis一级缓存的作用域是同一个SqlSession,在同一个...
  • mybatis缓存机制

    2018-10-20 21:55:26
    1.1 概述 像大多数持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库查询次数,从而提高性能。Mybatis缓存分为一级缓存,二级缓存。 从图中可以看出什么 ? 一级缓存是基于...
  • Mybatis缓存解析

    2018-08-02 21:49:13
    一、Mybatis的缓存概述 声明:本文纯属个人兴趣探究,其中一些概念、理论以及结论并不一定正确,如果有错误,请包涵并告知。 Mybatis作为持久层框架,要提升性能必然离不开缓存,Mybatis为我们提供了两级缓存,...
  • MyBatis 缓存

    2015-12-01 18:07:07
    缓存概述 MyBatis 同样提供了一级缓存和二级缓存的支持 正如大多数持久层框架一样,;一级缓存基于 PerpetualCache  HashMap 本地缓存,其存储作用域 为 Session,当 Session flush 或 close 之后,...
  • Mybatis缓存机制

    2019-07-18 15:44:52
    概述MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能 。 一、mybatis缓存机制的整体设计以及二级缓存的工作模式 如上图所示,当开启一个会话时,一个SqlSession...
  • 目录 概述 一级缓存(本地缓存) 一级缓存失效四种情况 1.当sqlSession不同时 2.当SqlSession相同,但是查询条件不同时 ...3.当SqlSession相同,但是多次查询之间进行了增删改时 ...Mybatis包含一个非常强大...
  • MyBatis(五)——MyBatis的缓存机制

    千次阅读 2018-03-13 18:49:11
    MyBatis的缓存机制可以极大的提升查询效率。  MyBatis系统中默认定义了两级缓存。一级缓存和二级缓存。    1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。  2、二级缓存需要...
  • Mybatis缓存机制详解

    千次阅读 2019-06-04 09:51:41
    概述 mybatis提供了缓存机制减轻数据库压力,提高数据库性能,其缓存分为两级:一级缓存、二级缓存。一级缓存是SqlSession级别的缓存,... mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先...
  • MyBatis-23MyBatis缓存配置【二级缓存

    千次阅读 2018-05-07 12:00:39
    二级缓存的配置 全局开关cacheEnabled Mapper.xml中配置二级缓存 Mapper接口中配置二级缓存 只使用注解方式配置二级缓存 同时使用注解方式和XML映射文件时 二级缓存的使用 前提:实体类实现Serializable接口 ...
  • Mybatis缓存——二级缓存概述示例配置二级缓存使用二级缓存二级缓存整合redis源码二级缓存标签配置文件 cacheEnabledMpper.xml :cache标签查询 概述 二级缓存的原理和一级缓存一样,同样是第一次查询会从数据库中...

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 270
精华内容 108
关键字:

概述mybatis的缓存