返回介绍

4.8 缓存 cache

发布于 2025-04-26 13:08:33 字数 7238 浏览 0 评论 0 收藏

缓存是互联网系统常常用到的,其特点是将数据保存在内存中。目前流行的缓存服务器有 MongoDB、Redis、Ehcache 等。缓存是在计算机内存上保存的数据,在读取的时候无需再从磁盘读入,因此具备快速读取和使用的特点,如果缓存命中率高,那么可以极大地提高系统的性能。如果缓存命中率很低,那么缓存就不存在使用的意义了,所以使用缓存的关键在于存储内容访问的命中率。

4.8.1 系统缓存(一级缓存和二级缓存)

MyBatis 对缓存提供支持,但是在没有配置的默认的情况下,它只开启一级缓存(一级缓存只是相对于同一个 SqlSession 而言)。

所以在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 Mapper 的方法,往往只执行一次 SQL,因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession 都只会取出当前缓存的数据,而不会再次发送 SQL 到数据库。

但是如果你使用的是不同的 SqlSesion 对象,因为不同的 SqlSession 都是相互隔离的,所以用相同的 Mapper、参数和方法,它还是会再次发送 SQL 到数据库去执行,返回结果。

让我们看看这样的例子,如代码清单 4-38 所示。

代码清单 4-38:测试 SqlSession 一级缓存

sqlSession = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
StudentBean student = studentMapper.getStudent(1);
logger.debug("使用同一个 sqlSession 再执行一次");
StudentBean student2 = studentMapper.getStudent(1);
//请注意,当我们使用二级缓存的时候,sqlSession 调用了 commit 方法后才会生效
sqlSession.commit();
logger.debug("现在创建一个新的 sqlSession 再执行一次");
sqlSession2 = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
StudentBean student3 = studentMapper2.getStudent(1);
//请注意,当我们使用二级缓存的时候,sqlSession 调用了 commit 方法后才会生效
sqlSession2.commit();

这里我们一共创建了两个 SqlSession 对象,第一个执行了两次查询,第二个执行了一次查询,让我们看看其运行的结果。

......
DEBUG 2016-03-23 14:17:58,461 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 36333492.
DEBUG 2016-03-23 14:17:58,466 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id, cnname, sex, note from t_student where id =? 
DEBUG 2016-03-23 14:17:58,507 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-03-23 14:17:58,590 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2016-03-23 14:17:58,591 com.learn.chapter4.main.Chapter4Main: 使用同一个 sqlSession 再执行一次
DEBUG 2016-03-23 14:17:58,591 com.learn.chapter4.main.Chapter4Main: 现在创建一个新的 sqlSession 再执行一次
DEBUG 2016-03-23 14:17:58,591 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2016-03-23 14:17:58,603 org.apache.ibatis.datasource.pooled.PooledD ataSource: Created connection 497359413.
DEBUG 2016-03-23 14:17:58,604 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select id, cnname, sex, note from t_student where id =? 
DEBUG 2016-03-23 14:17:58,604 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-03-23 14:17:58,605 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
DEBUG 2016-03-23 14:17:58,605 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC 4Connection@22a67b4]
......

我们发现第一个 SqlSession 实际只发生过一次查询,而第二次查询就从缓存中取出了,也就是 SqlSession 层面的一级缓存,它在各个 SqlSession 是相互隔离的。为了克服这个问题,我们往往需要配置二级缓存,使得缓存在 SqlSessionFactory 层面上能够提供给各个 SqlSession 对象共享。

而 SqlSessionFactory 层面上的二级缓存是不开启的,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis 要求返回的 POJO 必须是可序列化的,也就是要求实现 Serializable 接口,配置的方法很简单,只需要在映射 XML 文件配置就可以开启缓存了。

<cache/>

这样的一个语句里面,很多设置是默认的,如果我们只是这样配置,那么就意味着:

  • 映射语句文件中的所有 select 语句将会被缓存。

  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。

  • 缓存会使用默认的 Least Recently Used(LRU,最近最少使用的)算法来收回。

  • 根据时间表,比如 No Flush Interval,(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新。

  • 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。

  • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,不干扰其他调用者或线程所做的潜在修改。

添加了这个配置后,我们还必须做一件重要的事情,否则就会出现异常。这就是 MyBatis 要返回的 POJO 对象要实现 Serializable 接口,否则它就会抛出异常。做了这些修改后,我们再次测试代码清单 4-38,得到如下日志。

......
DEBUG 2016-03-23 14:19:47,411 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2016-03-23 14:19:47,631 org.apache.ibatis.datasource.pooled. PooledDataSource: Created connection 1730173572.
DEBUG 2016-03-23 14:19:47,634 org.apache.ibatis.logging.jdbc.Base JdbcLogger: ==>  Preparing: select id, cnname, sex, note from t_student where id =? 
DEBUG 2016-03-23 14:19:47,681 org.apache.ibatis.logging.jdbc.Base JdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-03-23 14:19:47,760 org.apache.ibatis.logging.jdbc.Base JdbcLogger: <==      Total: 1
DEBUG 2016-03-23 14:19:47,761 com.learn.chapter4.main.Chapter4Main: 使用同一个 sqlSession 再执行一次
DEBUG 2016-03-23 14:19:47,761 org.apache.ibatis.cache.decorators. LoggingCache: Cache Hit Ratio [com.learn.chapter4.mapper.StudentMapper]: 0.0
DEBUG 2016-03-23 14:19:47,770 com.learn.chapter4.main.Chapter4Main: 现在创建一个新的 sqlSession 再执行一次
DEBUG 2016-03-23 14:19:47,774 org.apache.ibatis.cache.decorators. LoggingCache: Cache Hit Ratio [com.learn.chapter4.mapper.StudentMapper]: 0.3333333333333333
DEBUG 2016-03-23 14:19:47,774 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC 4Connection@67205a84]
......

显然我们从头到尾只执行了一次 SQL,都是从二级缓存中得到我们需要的数据,这就说明二级缓存是在 SqlSessionFactory 层面所共享的。

当然我们也可以修改它们,代码清单 4-39 是使用其属性来修改的。

代码清单 4-39:配置缓存

<cache  eviction="LRU "  flushInterval="100000" size="1024" readOnly= "true"/>

这里我们讨论一下它们的属性。

  • eviction:代表的是缓存回收策略,目前 MyBatis 提供以下策略。

(1)LRU,最近最少使用的,移除最长时间不用的对象。

(2)FIFO,先进先出,按对象进入缓存的顺序来移除它们。

(3)SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象。

(4)WEAK,弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是 LRU,移除最长时间不用的对象。

  • flushInterval:刷新间隔时间,单位为毫秒,这里配置的是 100 秒刷新,如果你不配置它,那么当 SQL 被执行的时候才会去刷新缓存。

  • size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是 1024 个对象。

  • readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,它的默认值为 false,不允许我们修改。

4.8.2 自定义缓存

系统缓存是 MyBatis 应用机器上的本地缓存,但是在大型服务器上,会使用各类不同的缓存服务器,这个时候我们可以定制缓存,比如现在十分流行的 Redis 缓存。我们需要实现 MyBatis 为我们提供的接口 org.apache.ibatis.cache.Cache,缓存接口简介如代码清单 4-40 所示。

代码清单 4-40:缓存接口简介

//获取缓存编号
      String getId();
//保存 key 值缓存对象
  void putObject(Object key, Object value);
//通过 Key 获取缓存对象
  Object getObject(Object key);
//通过 key 删除缓存对象
  Object removeObject(Object key);
//清空缓存
  void clear();
//获取缓存对象大小
  int getSize();
//获取缓存的读写锁
  ReadWriteLock getReadWriteLock();

因为每种缓存都有其不同的特点,上面的接口都需要我们去实现,假设我们已经有一个实现类 com.learn.chapter4.MyCache,那么我们需要如代码 4-41 所示的配置。

代码清单 4-41:配置自定义缓存

<cache type="com.learn.chapter4.MyCache"/>

我们完成上述设置就能使用自定义的缓存了。而在缓存中你往往需要定制一些常用的属性,MyBatis 也对其做了支持,如代码清单 4-42 所示。

代码清单 4-42:设置自定义缓存参数

<cache type="com.learn.chapter4.MyCache">
  <property name="host" value="localhost/>
</cache>

如果我们在 MyCache 这个类增加 setHost(String host)方法,那么它在初始化的时候就会被调用,这样你可以对自定义设置一些外部参数。

我们在映射器上可以配置 insert、delete、select、update 元素,对应增、删、查、改这些内容。我们也可以配置 SQL 层面上的缓存规则,来决定它们是否需要使用或者刷新缓存,我们往往是根据两个属性:useCache 和 flushCache 来完成的,其中 useCache 表示是否需要使用缓存,而 flushCache 表示插入后是否需要刷新缓存。请看一个例子,如代码清单 4-43 所示。

代码清单 4-43:定制 SQL 执行缓存的策略

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。