天天看点

Mybatis入门系列之(十四)——二级缓存

二级缓存

MyBatis 的 二级缓存非常强大,它不同于一级缓存只存在于 SqlSession 的生命周期中,而是可以理解为存在于 Sq lSessionFactory 的生命周期中 。虽然目 前还没接触过同时存在多个 S qlSessionFactory 的情况,但可以知道,当存在多个 SqlSessioηFactory 时 , 它们的缓存都是绑定在各自对象上的,缓存数据在一般情况下是不相通的。只有在使用如 Redis这样的缓存数据库时,才可以共享缓存。

配置二级缓存

在mybatis-config.xml文件中settings标签里面增加如下配置(最后一个setting配置):

<settings>
    <!--
        ctrl+shift+u 大小写转换
        该属性配置执行LOG4J日志输出
    -->
    <setting name="logImpl" value="LOG4J"/>
    <!--
        开启驼峰映射
    -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!--
        配置懒加载的策略
    -->
    <setting name="aggressiveLazyLoading" value="false"/>
    <!--
        cacheEnabled,该参数是二级缓存的全局开关。默认值是true(可以不配置),即初始状态为开启状态。如果把该参数设置为false,即使后面有二级缓存配置,也不会生效。
        开启二级缓存的配置
    -->
    <setting name="cacheEnabled" value="true"/>
</settings>
           

MyBatis的二级缓存是和命名空间绑定的,即二级缓存需要配置在Mapper.xml映射文件中,或者配置在Mapper.java接口中。在映射文件中,命名空间就i是XML根节点mapper的namespace属性。在Mapper接口中,命名空间就是接口的全限定名称。

Mapper.xml文件中配置二级缓存

在保证二 级缓存的全局配置开启的情况下,给RoleMapper.xml开启 二 级缓存只需要在RoleMapper.xml中添加元素即可,添加后的RoleMapper.xml如下 :

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="tk.mybatis.simple.mapper.RoleMapper">
  <!--
        默认的二级缓存会有如下的效果:
        a. 映射语句文件中的所有select语句将会被缓存
        b.映射语句文件中的所有的insert 、update、delete语句会刷新缓存。
        c.缓存会使用Least Recently Used( LRU ,最近最少使用的)算法来收回。
        d.根据时间表(如no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新 。
        e. 缓存会存储集合或对象(无论查询方法返回什么类型的值)的 102 4 个引用。
        f. 地被调用者修改,而不干扰其他调用者或线程所做的潜在修改 。
            缓存会被视为 read/write(可读/可写)的 ,
            意味着对象检索不是共享的,而且可以安全
    -->
    <cache/>
  <!-- 其他配置 -->
</mapper>
           

所有的这些属性都可以通过缓存元素的属性来修改,示例如下:

<!--
   1. eviction(收回策略),有以下几种取值:
        1.1 LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值
        1.2 FIFO(先进先出):按照对象进入缓存的顺序来移除他们
        1.3 SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象
        1.4 WEAK(弱引用):更积极地移除基于垃圾回收器状态和弱引用规则的对象
   2. size(引用数目): 可以设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值1024
   3. flushInterval(刷新间隔):代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新
   4. readOnly(只读),true或者false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,
        这提供了很重要的性能优势。可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是false
-->
<cache eviction="FIFO"
       size="512"
       flushInterval="60000"
       readOnly="true"/>
           

Mapper接口中配置二级缓存

代码如下:

@CacheNamespace(
        eviction = FifoCache.class,
        size = ,
        flushInterval = ,
        readWrite = true)
public interface RoleMapper {}
           

当同时使用注解方式和 XML 映射文件时 , 如果同时配置了上述的二级缓存,就会抛出如下异常:

Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: 
Caches collection already contains value for tk.mybatis.simple.mapper.RoleMapper
           

使用参照缓存,可以解决问题:

RoleMapper接口中的配置:

@CacheNamespaceRef(RoleMapper.class)
public interface RoleMapper {}
           

或者在RoleMapper.xml文件中配置:

测试二级缓存的代码如下:

@Test
public void testL2Cache(){
    SqlSession sqlSession = getSqlSession();
    SysRole role1 = null;
    try{

        //获取RoleMapper接口
        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        //调用selectById方法,查询id=1的角色
        role1 = roleMapper.selectById(L);
        //对当前获取的对象重新赋值
        System.out.println("role1的名字----"+role1.getRoleName());
        role1.setRoleName("新的role名字");
        //再次查询获取id相同的用户
        SysRole role2 = roleMapper.selectById(L);
        //没有更新数据库,但是这个用户名和role1重新赋值的名字相同
        System.out.println("role2的名字----"+role1.getRoleName());
        Assert.assertEquals("新的role名字",role2.getRoleName());
        //role1和role2完全就是同一个实例
        Assert.assertEquals(role1,role2);
    }finally {
        sqlSession.close();
    }


    System.out.println("开启新的sqlSession");
    //开始另一个新的session
    sqlSession = getSqlSession();

    try{

        //获取RoleMapper接口
        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        //调用selectById方法,查询id=1的角色
        SysRole role2 = roleMapper.selectById(L);

        Assert.assertEquals("新的role名字",role2.getRoleName());
        //role1和role2完全就是同一个实例
        Assert.assertEquals(role1,role2);

        //获取role3
        SysRole role3 = roleMapper.selectById(L);
        //role2和role3完全就是同一个实例
        Assert.assertEquals(role2,role3);
    }finally {
        sqlSession.close();
    }
}
           

测试结果如下:

[DEBUG] 2018-05-01 18:48:10,106 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:62)

Cache Hit Ratio [tk.mybatis.simple.mapper.RoleMapper]: 0.0

[DEBUG] 2018-05-01 18:48:10,153 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)

==> Preparing: select id,role_name roleName,enabled,create_by createBy,create_time createTime from sys_role where id = ?

[DEBUG] 2018-05-01 18:48:10,200 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)

==> Parameters: 1(Long)

[TRACE] 2018-05-01 18:48:10,247 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.trace(BaseJdbcLogger.java:165)

<== Columns: id, roleName, enabled, createBy, createTime

[TRACE] 2018-05-01 18:48:10,247 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.trace(BaseJdbcLogger.java:165)

<== Row: 1, 管理员, 1, 1, 2018-04-24 14:34:34.0

[DEBUG] 2018-05-01 18:48:10,247 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)

<== Total: 1

role1的名字—-管理员

[DEBUG] 2018-05-01 18:48:10,247 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:62)

Cache Hit Ratio [tk.mybatis.simple.mapper.RoleMapper]: 0.0

role2的名字—-新的role名字

开启新的sqlSession

[DEBUG] 2018-05-01 18:48:10,294 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:62)

Cache Hit Ratio [tk.mybatis.simple.mapper.RoleMapper]: 0.3333333333333333

[DEBUG] 2018-05-01 18:48:10,294 method:org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:62)

Cache Hit Ratio [tk.mybatis.simple.mapper.RoleMapper]: 0.5

对测试结果的说明:

Mybatis入门系列之(十四)——二级缓存