Skip to content

13.缓存

​ MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

​ 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。要启用全局的二级缓存

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

13.1 默认缓存(一级缓存)

mybatis默认在会话级别开启缓存,select查询会触发缓存,当有更新数据则会刷新缓存

java
@Test
    public void testSelectByExample() {
        SqlSession session = factory.openSession();

        StudentMapper mapper = session.getMapper(StudentMapper.class);

        Student stu1 = mapper.selectByPrimaryKey(1);
        System.out.println("stu1:"+stu1);

        //执行update 触发缓存刷新
//        mapper.updateByPrimaryKey(stu1);


        Student stu2 = mapper.selectByPrimaryKey(1);
        System.out.println("stu2 = " + stu2);

        session.close();

    }

13.2 二级缓存

二级缓存默认关闭,需要手动的打开,在namespace层生效,可以在某一个不需要缓存的 Statement上加useCache="false"

  • 在SqlMapConfig.xml配置文件中设置cacheEnabled 为true

  • 在需要开启缓存的Mapper.xml添加<cache/> 标记

    xml
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
  • 可用的清除策略有:

    • LRU – 最近最少使用:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  • flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

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

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

  • 当SQLSession执行commit或者close的时候才添加二级缓存的数据

使用

在sqlmapconfig.xml中开启

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>


    <properties resource="db/jdbc.properties">
    </properties>
       
    <settings>
        <!--        打印sql -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--        延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>

<!--        3.4.1 之前的版本(true) 也得加上 aggressiveLazyLoading false-->
        <setting name="aggressiveLazyLoading" value="false"/>

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

    </settings>


<!--    ll  ls -l 类型别名-->
    
    <typeAliases>
<!--        <typeAlias type="com.neuedu.mybatis.entity.UmsUser" alias="User"/>-->
<!--        <typeAlias type="com.neuedu.mybatis.entity.Dept" alias="Dept"/>-->
        <package name="com.neuedu.mybatis.entity"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
<!--            链接数据的信息-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>


    </environments>


    <mappers>

        <!-- 每张表的 statement-->
<!--        <mapper resource="mapper/DeptMapper.xml"/>-->
<!--        <mapper resource="mapper/UmsUserMapper.xml"/>-->

        <!--   1 xml:namespace 跟 接口的全限定名保持一致
               2 接口的方法名 跟 statement 的 id 保持一致
               3 接口的参数类型 跟 statement 的 parameterType 保持一致
               4 接口的返回值类型 跟 statement 的 resultType 保持一致
               5 路径得保持一致 :
                Mapper.java 的路径: com.neuedu.mybatis.mapper.  XXXX。Java
                Mapper.xml  的路径   com.neuedu.mybatis.mapper.  XXXX。xml
             -->
<!--        <mapper class="com.neuedu.mybatis.mapper.DeptMapper"></mapper>-->
<!--        <mapper class="com.neuedu.mybatis.mapper.UmsUserMapper"></mapper>-->


        <!--        要求 同  class的方式一样-->
        <package name="com.neuedu.mybatis.mapper"/>

    </mappers>
</configuration>

在某一个Mapper.xml中的namespace中添加 <cache/>标签

在Java中使用

java
@Test
public void testNamespaceCache() throws IOException {
    SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getInstance();
    SqlSession session1 = sqlSessionFactory.openSession();
    SqlSession session2 = sqlSessionFactory.openSession();



    TeacherExample example = new TeacherExample();
    TeacherMapper teacherMapper1 = session1.getMapper(TeacherMapper.class);
    List<Teacher> teachers = teacherMapper1.selectByExample(example);
    System.out.println("teachers = " + teachers);

    //触发二级缓存  存储数据
    session1.commit();


    TeacherMapper teacherMapper2 = session2.getMapper(TeacherMapper.class);
    List<Teacher> teachers2 = teacherMapper2.selectByExample(example);
    List<Teacher> teachers3= teacherMapper2.selectByExample(example);
    System.out.println("teachers2 = " + teachers2);

    session1.close();
    session2.close();
}

注意:

二级缓存不是查完设置的缓存,需要sqlsession对象 调用commit 或者close 方法

控制台输出

txt
Cache Hit Ratio [com.neuedu.mybatis.mapper.TeacherMapper]: 0.0
Opening JDBC Connection
Created connection 1086849943.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@40c80397]
==>  Preparing: select teacher_id, name, age, salary from teacher
==> Parameters: 
<==    Columns: teacher_id, name, age, salary
<==        Row: 1, 张飞, 50, 5555.01
<==        Row: 2, 赵云, 55, 8873.00
<==        Row: 3, 刘备, 66, 88.00
<==      Total: 3
teachers = [Teacher [Hash = 2014563089, teacherId=1, name=张飞, age=50, salary=5555.01, serialVersionUID=1], Teacher [Hash = 198098993, teacherId=2, name=赵云, age=55, salary=8873.00, serialVersionUID=1], Teacher [Hash = 553672556, teacherId=3, name=刘备, age=66, salary=88.00, serialVersionUID=1]]
Cache Hit Ratio [com.neuedu.mybatis.mapper.TeacherMapper]: 0.5
Cache Hit Ratio [com.neuedu.mybatis.mapper.TeacherMapper]: 0.6666666666666666
teachers2 = [Teacher [Hash = 2024711353, teacherId=1, name=张飞, age=50, salary=5555.01, serialVersionUID=1], Teacher [Hash = 711310213, teacherId=2, name=赵云, age=55, salary=8873.00, serialVersionUID=1], Teacher [Hash = 1267042315, teacherId=3, name=刘备, age=66, salary=88.00, serialVersionUID=1]]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@40c80397]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@40c80397]
Returned connection 1086849943 to pool.

13.3 Mybatis的默认缓存实现 :

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.impl;

import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

13.4 使用Ehcache类库

13.4.1 添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mybatis-parent-java1</artifactId>
        <groupId>com.neuedu.mybatis</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>08-mybatis-cache</artifactId>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>


        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <!--第三方的缓存类库 -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.2</version>
        </dependency>
        <!-- 整合Ehcache 和mybatis的Cache -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.1.0</version>
        </dependency>


    </dependencies>

</project>

13.4.2 开启二级缓存

在sqlMapConfig.xml中设置 缓存启动

xml
 <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>

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

13.4.3 在namespace 中添加Ehcache的缓存

xml
  <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

13.4.4 添加Ehcache主配置文件

xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

    <!--diskStore:缓存数据持久化的目录 地址  -->
    <diskStore path="D:\ehcache" />

    <defaultCache
            maxEntriesLocalHeap="0"
            eternal="false"
            diskPersistent = "true"
            timeToIdleSeconds="1200"
            timeToLiveSeconds="1200">
    </defaultCache>
    
</ehcache>

Released under the MIT License.