一、缓存简介

什么是缓存

  • 存在内存中的临时数据
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库的数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题

    为什么使用缓存

  • 减少和数据库的交互次数,减少系统开销,提高系统效率

    什么样的数据能使用缓存

  • 经常查询并且不经常改变的数据

    MyBatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率

  • Mybatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启(一级缓存即是SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置(二级缓存是基于namespace级别的缓存)
    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口自定义二级缓存

      搭建环境、测试

      ```sql create database mybatis;

use mybatis;

create table user ( id int(20) not null primary key, name varchar(30) default null, pwd varchar(30) default null )engine=innodb charset=utf-8;

insert into user (id, name, pwd) values (1, ‘comprehensive’, ‘123456’), (2, ‘何光伦’, ‘234567’), (3, ‘焦锦彪’, ‘345678’), (4, ‘李杰亮’, ‘456789’), (5, ‘周希程’, ‘56789’);

  1. ```xml
  2. <!-- Maven项目的xml文件——导入依赖 -->
  3. <dependencies>
  4. <dependency>
  5. <groupId>log4j</groupId>
  6. <artifactId>log4j</artifactId>
  7. <version>1.2.17</version>
  8. </dependency>
  9. </dependencies>
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/comprehensive.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
<?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">
<!-- UserMapper.xml映射器文件 -->
<mapper namespace="com.comprehensive.dao.UserMapper">
    <select id="getUsers" resultType="com.comprehensive.pojo.User">
        select * from mybatis.user
    </select>
    <select id="getUserById" parameterType="_int" resultType="com.comprehensive.pojo.User">
        select * from mybatis.user where id=#{id}
    </select>
    <update id="updateUserById" parameterType="map">
        update mybatis.user
        <set>
            <if test="name != null">
                name=#{name},
            </if>
            <if test="pwd != null">
                pwd=#{pwd}
            </if>
        </set>
        where id=#{id}
    </update>
</mapper>
<?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>
    <!-- mybatis-config.xml文件——配置日志实现 -->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;characterSetResults=utf-8&amp;useSSL=false&amp;verifyServerCertificate=false&amp;autoReconnect=true&amp;autoReconnectForPools=true&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="txq199807031578"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 每一个mapper.xml都需要在MyBatis核心配置文件中注册 -->
    <mappers>
        <mapper resource="com/comprehensive/dao/UserMapper.xml"/>
    </mappers>
</configuration>
//User实体类
public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {}

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
//UserMapper接口
public interface UserMapper {
    List<User> getUsers();
    User getUserById(int id);
    void updateUserById(int id);
}

二、一级缓存

一级缓存测试

public class Test_cache {
    @Test
    public void test_cacheLevelOne() {
        SqlSession sqlSession = MyBatis_Util.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.getUserById(1);
        System.out.println("user1: " + user1);
        System.out.println("================此处为分割线=================");
        User user2 = mapper.getUserById(1);
        System.out.println("user2: " + user2);
        System.out.println("user1 == user2 ? " + user1.equals(user2));
        sqlSession.close();
    }
}

image.png

一级缓存失效的情况

  • 一级缓存中不存在本次查询的对象

    public class Test_cache {
      @Test
      public void test_cacheLevelOne() {
          SqlSession sqlSession = MyBatis_Util.getSqlSession();
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          User user1 = mapper.getUserById(1);
          System.out.println("user1: " + user1);
          System.out.println("================此处为分割线=================");
          User user2 = mapper.getUserById(2);
          System.out.println("user2: " + user2);
          System.out.println("user1 == user2 ? " + user1.equals(user2));
          sqlSession.close();
      }
    }
    

    image.png

  • 一级缓存刷新

增删改操作,可能会改变原来的数据,必定会刷新一级缓存

三、二级缓存

具体的可以参照官方文档
image.png

二级缓存工作机制

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
  • 如果当前会话关闭,这个会话对应的一级缓存就会消失;但是,会话关闭时,一级缓存中的数据会被保存到二级缓存中
  • 新的会话查询信息,就可以从二级缓存中获取内容
  • 不同的mapper映射器文件查询出来的数据会放在它对应的二级缓存中

    二级缓存测试

  • 开启全局缓存

image.png

<?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>
    <!-- mybatis-config.xml文件——配置日志实现 -->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;characterSetResults=utf-8&amp;useSSL=false&amp;verifyServerCertificate=false&amp;autoReconnect=true&amp;autoReconnectForPools=true&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="txq199807031578"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 每一个mapper.xml都需要在MyBatis核心配置文件中注册 -->
    <mappers>
        <mapper resource="com/comprehensive/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 在映射器文件中开启二级缓存

    <?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">
    <!-- UserMapper.xml映射器文件 -->
    <mapper namespace="com.comprehensive.dao.UserMapper">
      <!-- 在当前映射器文件中,使用二级缓存 -->
      <cache
          eviction="FIFO"
          flushInterval="60000"
          size="512"
          readOnly="true"/>
      <select id="getUsers" resultType="com.comprehensive.pojo.User">
          select * from mybatis.user
      </select>
      <select id="getUserById" parameterType="_int" resultType="com.comprehensive.pojo.User">
          select * from mybatis.user where id=#{id}
      </select>
      <update id="updateUserById" parameterType="map">
          update mybatis.user
          <set>
              <if test="name != null">
                  name=#{name},
              </if>
              <if test="pwd != null">
                  pwd=#{pwd}
              </if>
          </set>
          where id=#{id}
      </update>
    </mapper>
    
  • 开始测试

    public class Test_cache {
      @Test
      public void test_cacheLevelTwo() {
          SqlSession sqlSession1 = MyBatis_Util.getSqlSession();
          UserMapper mapper = sqlSession1.getMapper(UserMapper.class);
          User user1 = mapper.getUserById(1);
          sqlSession1.close();
          System.out.println("====第一次会话关闭,第二次会话开启====");
          SqlSession sqlSession2 = MyBatis_Util.getSqlSession();
          UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
          User user2 = mapper2.getUserById(1);
          sqlSession2.close();
          System.out.println("user1 == user2 ? " + user1.equals(user2));
      }
    }
    

    image.png

    四、MyBatis缓存原理

    image.png