跳到主要内容

19、MyBatis速成 - 二级缓存

默认情况下是只开启了局部的sqlSession缓存(一级缓存),可以增强变现而且处理循环 依赖也是必须的。

1.缓存配置

要开启二级缓存,需要做一些配置
1)开启所有映射器中缓存配置:
将value设置为true

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

2)需要在你的 SQL 映射文件中添加一行:

<cache/>

字面上看就是这样。这个简单语句的效果如下:

·映射语句文件中的所有 select 语句将会被缓存。
·映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
·缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
·根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
·缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
·缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

所有的这些属性都可以通过缓存元素的属性来修改。比如:

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

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。

可用的收回策略有:

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

默认的是 LRU。

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

2.自定义缓存

使用自定义缓存
除了这些自定义缓存的方式, 你也可以通过实现你自己的缓存或为其他第三方缓存方案 创建适配器来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示例展 示了 如何 使用 一个 自定义 的缓 存实 现。type 属 性指 定的 类必 须实现 org.mybatis.cache.Cache 接口。这个接口是 MyBatis 框架中很多复杂的接口之一,但是简单 给定它做什么就行。

public interface Cache {
   
     
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

要配置你的缓存, 简单和公有的 JavaBeans 属性来配置你的缓存实现, 而且是通过 cache 元素来传递属性, 比如, 下面代码会在你的缓存实现中调用一个称为 “setCacheFile(String file)” 的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有简单类型作为 JavaBeans 的属性,MyBatis 会进行转换。 And you can specify a placeholder(e.g. ${cache.file}) to replace value defined at configuration properties.

从3.4.2版本开始,MyBatis已经支持在所有属性设置完毕以后可以调用一个初始化方法。如果你想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口。

public interface InitializingObject {
   
     
  void initialize() throws Exception;
}

记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此,所有 在相同命名空间的语句正如绑定的缓存一样。 语句可以修改和缓存交互的方式, 或在语句的 语句的基础上使用两种简单的属性来完全排除它们。默认情况下,语句可以这样来配置:

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

因为那些是默认的,你明显不能明确地以这种方式来配置一条语句。相反,如果你想改 变默认的行为,只能设置 flushCache 和 useCache 属性。比如,在一些情况下你也许想排除 从缓存中查询特定语句结果,或者你也许想要一个查询语句来刷新缓存。相似地,你也许有 一些更新语句依靠执行而不需要刷新缓存。

3.参照缓存

回想一下上一节内容, 这个特殊命名空间的唯一缓存会被使用或者刷新相同命名空间内 的语句。也许将来的某个时候,你会想在命名空间中共享相同的缓存配置和实例。在这样的 情况下你可以使用 cache-ref 元素来引用另外一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

4.示例

创建实体类
Employee,这里需要实现Serializable接口

package org.mybatis.cache2;
import java.io.Serializable;
public class Employee implements Serializable{
   
     
    private Integer id;
    private String lastName;
    private String email;
    private String gender;

    public Employee() {

    }

    public Employee(Integer id, String lastName, String email, String gender) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
    }

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String toString() {
        return "Employee [id=" + id + ", lastName=" + lastName + ", email="
                + email + ", gender=" + gender + "]";
    }

}

创建方法接口
EmployeeMapper

package org.mybatis.cache2;
public interface EmployeeMapper {
   
     
    public Employee getEmpById(Integer id);
    public void addEmp(Employee emp);
}

创建主配置文件
mybatis-config-cache2.xml在<settings> 标签中开启二级缓存全局开关配置
<setting name="cacheEnabled" value="true"/>

<?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>

    <properties resource="db.properties"></properties>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="lazyLoadingEnabled" value="true" />
        <setting name="aggressiveLazyLoading" value="false"/>

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

    <typeAliases>
        <!-- 为包下的所有类起别名,默认为类名大小写(不区分大小写) -->
        <package name="org.mybatis.cache2"/>
    </typeAliases>

    <!-- 默认development是开发环境,如果改成test则表示使用测试环境 -->
    <environments default="dev_mysql">
        <environment id="dev_mysql">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${mysql.driver}" />
                <property name="url" value="${mysql.url}" />
                <property name="username" value="${mysql.username}" />
                <property name="password" value="${mysql.password}" />
            </dataSource>
        </environment>

        <environment id="test_mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${mysql.drivertest}" />
                <property name="url" value="${mysql.urltest}" />
                <property name="username" value="${mysql.usernametest}" />
                <property name="password" value="${mysql.passwordtest}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/cache2mapper.xml"/>
    </mappers>
</configuration>

创建mapper配置文件
cache2mapper.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="org.mybatis.cache2.EmployeeMapper">

    <cache/>

    <select id="getEmpById" resultType="org.mybatis.cache2.Employee" useCache="true">
        select * from mybatis_employee me where me.id ={id}
    </select>

    <insert id="addEmp" parameterType="org.mybatis.cache2.Employee" useGeneratedKeys="true" keyProperty="id">
        insert into mybatis_employee 
        (last_name,email,gender) 
        values 
        (#{lastName},#{email},#{gender});
    </insert>

</mapper>

创建测试类

package org.mybatis.cache2;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
/**
 * 测试二级缓存
 * @author DDKK.COM 弟弟快看,程序员编程资料站
 */
public class MybatisTest {
   
     

    @Test
    public void testMybatis() {
        String resource = "mybatis-config-cache2.xml";//全局配置文件

        InputStream inputStream = null;
        SqlSessionFactory sqlSessionFactory = null;

        try {
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

            SqlSession sqlSession1 = sqlSessionFactory.openSession();
            EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);

            Employee emp1 = mapper1.getEmpById(2);
            System.out.println(emp1);
            sqlSession1.close();

            SqlSession sqlSession2 = sqlSessionFactory.openSession();
            EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);

            Employee emp2 = mapper2.getEmpById(2);
            System.out.println(emp2);
            sqlSession2.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
    }
}

测试结果:

DEBUG - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@704921a5]
DEBUG - ==> Preparing: select * from mybatis_employee me where me.id = ?
DEBUG - ==> Parameters: 2(Integer)
Employee [id=2, lastName=lisi, email=tang_greatman@qq.com, gender=1]
DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@704921a5]
DEBUG - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@704921a5]
DEBUG - Returned connection 1883840933 to pool.
DEBUG - Cache Hit Ratio [org.mybatis.cache2.EmployeeMapper]: 0.5
Employee [id=2, lastName=lisi, email=tang_greatman@qq.com, gender=1]

不同的sqlSession(不同sqlSession一级缓存不同,具体参考上一篇mybatis详解-(18)一级缓存),在第二次执行相同的查询时,数据是从二级缓存中直接获取的。
当然这里需要注意的是:二级缓存是在sqlSession关闭的时候才进行保存的
如果上面的demo把sqlSession1.close()放在第二次查询之后关闭,那么第二次查询时无法获取二级缓存的。

5.关闭缓存

1)主配置文件中设置cacheEnabled为false
<setting name="cacheEnabled" value="false"/>

2)mapper配置文件中去掉<cache>
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

3)将查询<select> 标签useCache属性设置为false
<select useCache="false">

4)将查询<select> 标签flushCache属性设置为true
<select flushCache="true">

6.二级缓存原理

二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。
一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
一级缓存是默认使用的。
二级缓存需要手动开启。
 
图片引自mybatis–查询缓存