4.8 缓存 cache
缓存是互联网系统常常用到的,其特点是将数据保存在内存中。目前流行的缓存服务器有 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论