Mybatis基础学习之一级缓存和二级缓存的简单使用

前言

小伙伴们,大家好,我是狂奔の蜗牛rz,当然你们可以叫我蜗牛君,我是一个学习Java半年多时间的小菜鸟,同时还有一个伟大的梦想,那就是有朝一日,成为一个优秀的Java架构师。

这个Mybatis基础学习系列是用来记录我学习Mybatis框架基础知识的全过程 (这个系列是参照B站狂神的Mybatis最新教程来写的,由于是之前整理的,但当时没有发布出来,所以有些地方可能有错误,希望大家能够及时指正!)


之后我将尽量以两天一更的速度更新这个系列,还没有学习Mybatis3框架的小伙伴可以参照我的博客学习一下;当然学习过的小伙伴,也可以顺便跟我一起复习一下基础。最后,希望能够和大家一同进步吧!加油吧!少年们!

特别提醒:如果对Mybatis基础学习系列感兴趣,可以阅读本系列往期博客:
第一篇:Mybatis基础学习之初识Mybatis
第二篇:Mybatis基础学习之第一个Mybatis程序
第三篇:Mybatis基础学习之CRUD增删改查
第四篇:Mybatis基础学习之万能的Map和模糊查询
第五篇: Mybatis基础学习之配置解析(上篇)
第六篇: Mybatis基础学习之配置解析(下篇)
第七篇: Mybatis基础学习之使用ResultMap解决字段名不一致
第八篇: Mybatis基础学习之日志工厂的简单使用
第九篇: Mybatis基础学习之数据分页的简单使用
第十篇: Mybatis基础学习之使用注解开发
第十一篇: Mybatis基础学习之Lombok的简单使用
第十二篇: Mybatis基础学习之多对一关系处理
第十三篇: Mybatis基础学习之一对多关系处理
第十四篇: Mybatis基础学习之动态SQL的简单使用


今天我们来到了Mybatis基础学习的第十四站:动态SQL的简单使用。废话不多说,让我们开始今天的学习内容吧。

14 一级缓存和二级缓存的简单使用

14.1 简介

你是否思考过,如果每次查询数据都需要连接数据库,这样做会不会比较耗费资源?但如果我们把某一次查询的结果,给它暂存在一个可以直接取到的地方,例如内存(缓存);当再次查询相同数据的时候,可以直接走缓存,而不用走数据库了!那到底 什么是缓存 呢?下面来简单介绍一些缓存相关的基本概念

14.1.1 什么是缓存

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

14.1.2 为什么使用缓存

下面是一个系统设计架构图,在多台服务器和多个数据库之间,我们可以使用缓存减少和数据库的交互次数,减少系统开销,提高系统效率

Mybatis基础学习之一级缓存和二级缓存的简单使用_第1张图片

14.1.3 什么样的数据能使用缓存

经常查询并且不经常改变的数据可以使用缓存

简单了解了缓存的基本概念后,我们来看一下Mybatis中的缓存是如何定义的

14.2 MyBatis缓存

14.2.1 什么是MyBatis缓存

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

14.2.2 MyBatis中的两级缓存

MyBatis默认定义了两级缓存一级缓存二级缓存

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

14.2.3 MyBatis中的清除策略

Mybatis中的清除策略主要包含以下四种:

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

注意Mybatis默认的清除策略LRU

简单了解完Mybatis中的缓存定义和清除策略,下面我们来重点介绍一下一级缓存二级缓存

14.3 一级缓存

14.3.1 一级缓存概念

  • 一级缓存也叫本地缓存SqlSession级别的缓存
  • 数据库同一次会话期间查询到的数据会放在本地缓存
  • 以后如果需要获取相同的数据直接从缓存中拿不需要再去查询数据库

14.3.2一级缓存的使用

1.创建项目和导入资源依赖

  • 创建一个Maven项目,在pom.xml文件中导入资源依赖jar包

<dependencies>
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.18.10version>
    dependency>
dependencies>

2.编写核心配置文件

1-1 编写db.properties配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
pwd=123456
1-2 编写mybatis-config.xml配置文件

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="logImpl" value="STDOUT_LOGGING"/>
    settings>
    
    
    <typeAliases>
        <package name="com.kuang.pojo"/>
    typeAliases>
    
    
    <environments default="development">
        
        <environment id="development">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${pwd}"/>
            dataSource>
        environment>
        
    environments>
    
    
    <mappers>
        
        <mapper class="com.kuang.dao.UserMapper"/>
    mappers>
    
configuration>

3.编写实体类和工具类

3-1 编写User实体类
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data // 使用@Data注解, 引入无参构造, get和set方法以及toString方法
@AllArgsConstructor // 引入有参构造方法
@NoArgsConstructor // 引入无参构造方法
public class User {
    
    private int id; // 用户编号
    private String name; // 用户名
    private String pwd; // 密码
    
}
3-2 编写MybatisUtils工具类
package com.kuang.utils;

 /**
  * SqlSessionFactoryBuilder -- 建造工厂
  * --> sqlSessionFactory -- 生产sqlSession
  * --> sqlSession  
  */
public class MybatisUtils {
    
    // 获取SqlSessionFactory
    private static SqlSessionFactory sqlSessionFactory;
    
    // 静态方法体
    static {
        try {
            // 读取配置文件
            String resource = "mybatis-config.xml";
            // 解析配置文件流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 获取工厂
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
   /**
    * SqlSession提供了在数据库执行SQL命令所需的所有方法
    */
   public static SqlSession getSqlSession() {
       // 设置参数为true,实现自动提交
       return sqlSessionFactory.openSession(true);
   }
    
}

4.编写Mapper接口及配置文件

4-1 编写Mapper接口
package com.kuang.dao;

public interface UserMapper {
    
    // 根据id查询指定用户
    User queryUserById(@Param("id") int id);
    
    // 更新用户信息
    int updateUser(User user);
    
}
4-2 编写UserMapper.xml

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper">
    
	
    
    <select id="queryUserById"  resultType="user">
        Select * from user where id = #{id}
    select>

    
    <update id="updateUser" parameterType="User">
        Update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}
    update>    

mapper>

5.编写测试类和测试结果

5-1 编写MyTest测试类
  • 测试一个Session中查询两次相同记录
package com.kuang.dao;

public class MyTest { 
    
	// 通过id查询指定的用户信息
    @Test
    public void queryUserById() {
        
        // 获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 获取UserMapper接口
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        // 调用queryUserById方法,查询id为1的用户信息
        User user = mapper.queryUserById(1);
        System.out.println(user);
        
        System.out.println("==================");
        
        // 调用queryUserById方法,再次查询id为1的用户信息
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        
        // 判断前后两次查询的用户是否相等
        System.out.println(user==user2);
        
        // 关闭sqlSession对象
        sqlSession.close();
    }
    
}
5-2 测试结果

Mybatis基础学习之一级缓存和二级缓存的简单使用_第2张图片

结果成功查询到了id为1的两条用户信息!

6.测试总结

经过观察后发现,预编译sql只执行了一次,但是两条记录都被查询出来了,因此此过程中没有刷新缓存;由于user==user2的输出值为“true”,可以确定两条用户信息相同;因此,在同一个Session中成功查询到了两条相同的记录!

14.3.3 缓存失效

1.编写MyTest测试类

1-1 一个Session中进行增删改
  • 测试在一个Session中使用不同的方法:例如查询和修改用户
package com.kuang.dao;

public class MyTest {
    
    // 查询和修改用户信息
    @Test
    public void updateUser() {
        
        // 获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 获取UserMapper接口
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        // 调用queryUserById方法,查询id为1的用户信息
        User user = mapper.queryUserById(1);
        // 打印查询到的用户信息
        System.out.println(user);
        // 调用方法updateUser,修改id为6的用户信息
        mapper.updateUser(new User(6,"张学友","zxy123456"));
        
        System.out.println("==================");
        
        // 调用queryUserById方法,再次查询id为1的用户信息
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        
        // 判断前后两次查询的用户是否相等
        System.out.println(user==user2);
        
        // 关闭sqlSession对象
        sqlSession.close();
    }
    
}
1-2 手动清理缓存
  • 测试一个Session查询两次相同记录,然后手动设置清除缓存
package com.kuang.dao;
public class MyTest {    
    
	// 通过id查询指定的用户信息
    @Test
    public void queryUserById() {

        // 获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 获取UserMapper接口
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        // 调用queryUserById方法,查询id为1的用户信息
        User user = mapper.queryUserById(1);
        System.out.println(user);
        
         // 手动清理缓存
        sqlSession.clearCache();
        System.out.println("==================");
        
        // 调用queryUserById方法,查询id为1的用户信息
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        
        // 判断前后两次查询的用户是否相等
        System.out.println(user==user2);
        
        // 关闭sqlSession对象
        sqlSession.close();
    }
    
}

2.测试结果及分析

2-1 一个Session中修改和查询

Mybatis基础学习之一级缓存和二级缓存的简单使用_第3张图片

结果成功查询到了两次id为1的用户信息,并且更新了id为6的用户信息!

分析

虽然两次都先后查到了id为1的用户信息,但预编译的sql却在更新id为6的用户信息前后,分别执行了两次。因此可以确定,在此过程中刷新了缓存!

2-2 手动清理缓存

Mybatis基础学习之一级缓存和二级缓存的简单使用_第4张图片

结果成功查询到了两条id为1的用户信息!

分析

虽然先后两次都查询到了id为1的用户信息,但查询完每条信息后,却再次预编译sql语句,因此,此过程中刷新了缓存

3.测试总结

缓存失效的情况

  • 查询不同的信息
  • 增删改操作可能会改变原来的数据,所以必定会刷新缓存
  • 查询不同的Mapper.xml
  • 手动清理缓存

14.3.4 一级缓存总结

一级缓存是默认开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段,一级缓存相当于是一个Map,用的时候直接去取就可以了

14.4 二级缓存

14.4.1 二级缓存概念

二级缓存也叫全局缓存一级缓存作用域太低了,所以诞生了二级缓存,它基于namespace级别的缓存,一个空间名称,对应一个二级缓存

14.4.2 二级缓存工作机制

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存
  • 如果当前会话关闭了,这个会话对应的一级缓存就没有了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存
  • 新的会话查询信息,就可以从二级缓存中获取内容
  • 不同的mapper查出的数据,会放在自己对应的缓存(map)中

14.4.3 开启二级缓存步骤

默认情况下,只启用了本地的会话缓存(即一级缓存),它仅仅对一个会话中的数据进行缓存,要启用全局的二级缓存,只需要在SQL映射文件中添加标签即可

1.开启全局缓存

  • 在核心配置文件mybatis-config.xml文件显式开启全局缓存

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

2.在对应Mapper.xml中开启二级缓存

  • UserMapper.xml映射文件中使用标签开启二级缓存
2-1 使用原始的标签

<cache/>
2-2 使用自定义的标签

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

cache标签参数解释

eviction=“FIFO”缓存清除策略为FIFO(即先进先出)
flushInterval=“60000”刷新间隔为60秒
size=“512”最多可以存储结果对象或列表的512个引用
readOnly=“true”返回的对象是只读的

14.4.4 二级缓存的使用测试

1.核心配置文件中开启全局缓存

  • 在核心配置文件mybatis-config.xml文件显式开启全局缓存

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="logImpl" value="STDOUT_LOGGING"/>
    settings>
    
    
    <setting name="cacheEnabled" value="true"/>settings>
    
    
    <typeAliases>
        <package name="com.kuang.pojo"/>
    typeAliases>
    
    
    <environments default="development">
        
        <environment id="development">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${pwd}"/>
            dataSource>
        environment>
        
    environments>
    
    
    <mappers>
        
        <mapper class="com.kuang.dao.UserMapper"/>
    mappers>
    
configuration>

2.在对应Mapper.xml映射文件中开启二级缓存

2-1 不使用二级缓存

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper">
    
    
    
    <select id="queryUserById"  resultType="user">
        Select * from user where id = #{id}
    select>
    
    
    
    <update id="updateUser" parameterType="User" flushCache="false">
        Update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}
    update>
    
mapper>
2-2 只使用标签开启二级缓存
  • UserMapper.xml映射文件中使用自定义的标签开启二级缓存

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper">
    
    
    
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    
    
    
    <select id="queryUserById"  resultType="user" useCache="true">
        Select * from user where id = #{id}
    select>
    
    
    
    <update id="updateUser" parameterType="User" flushCache="true">
        Update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}
    update>
    
mapper>

3.编写MyTest测试类

3-1 不使用二级缓存
package com.kuang.dao;

public class MyTest {    
    
   // 通过id查询指定的用户信息
    @Test
    public void queryUserById2() {
        
        // 分别获取两个sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        
        // 分别获取两个Mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        
        // 调用queryUserById方法,查询id为1的用户信息
        User user = mapper.queryUserById(1);
        // 调用queryUserById方法,再次查询id为1的用户信息
        User user2 = mapper2.queryUserById(1);
        
        // 打印用户信息
        System.out.println(user);
        System.out.println(user2);
        // 判断两个用户信息是否相等
        System.out.println(user==user2);
        
        // 分别关闭两个sqlSession对象
        sqlSession.close();
        sqlSession2.close();
    }
    
}
3-2 使用二级缓存
package com.kuang.dao;

public class MyTest {  
    
   // 通过id查询指定的用户信息
    @Test
    public void queryUserById2() {
        
        // 分别获取两个sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();

        // 获取第一个Mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 调用queryUserById方法,查询id为1的用户信息
        User user = mapper.queryUserById(1);
        // 打印用户信息
        System.out.println(user);
        // 关闭第一个sqlSession对象
        sqlSession.close();

        // 获取第二个Mapper接口对象
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        // 调用queryUserById方法,再次查询id为1的用户信息
        User user2 = mapper2.queryUserById(1);
        // 打印用户信息
        System.out.println(user2);
        // 判断两个用户信息是否相等
        System.out.println(user==user2);
        // 关闭第二个sqlSession对象
        sqlSession2.close();
        
    }
    
}

4.测试结果

4-1 不使用二级缓存

Mybatis基础学习之一级缓存和二级缓存的简单使用_第5张图片

结果两次都成功查询到了id为1的用户信息!

分析

观察结果后发现,预编译sql执行了两次,因此每查询完一条信息后就刷新了一次缓存

4-2 只使用标签开启二级缓存

Mybatis基础学习之一级缓存和二级缓存的简单使用_第6张图片

结果Caused by: ava.io.NotSerializableException: com.kuang.pojo.User

分析

导致报错的原因是,com.kuang.pojo包下的User实体类没有序列化

结论因此,需要将实体类序列化,否则将会报错

4-3 使用自定义的cache标签开启二级缓存

Mybatis基础学习之一级缓存和二级缓存的简单使用_第7张图片

结果两次也都成功查询到了id为1的用户信息!

分析

使用了二级缓存后,预编译sql只执行了一次;当第一条用户信息查询完关闭sqlSession后,一级缓存虽然关闭了,但其数据被保存到了二级缓存中;因此第二次查询相同的用户信息,直接从二级缓存取出,不需要访问数据库和执行预编译sql语句了

14.4.5 解决实体类未序列化问题

1.修改User实体类

  • User实体类实现Serializable接口,从而实现序列化
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@Data // 使用@Data注解, 引入无参构造、get、set、toString等方法
@AllArgsConstructor // 使用@AllArgsConstructor注解, 引入有参构造方法
@NoArgsConstructor // 使用@NoArgsConstructor注解, 再次引入无参构造方法

// 让User实体类实现Serializable接口,从而实现序列化
public class User implements Serializable {
    
    private int id;
    private String name;
    private String pwd;
    
}

2.编写MyTest测试类

  • 设置两个sqlSession对象,查询同一条用户数据
package com.kuang.dao;

public class MyTest {    
    
   // 通过id查询指定的用户信息
    @Test
    public void queryUserById2() {
        
        // 分别获取两个sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();

        // 获取第一个Mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 调用queryUserById方法,查询id为1的用户信息
        User user = mapper.queryUserById(1);
        // 打印用户信息
        System.out.println(user);
        // 关闭第一个sqlSession对象
        sqlSession.close();

        // 获取第二个Mapper接口对象
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        // 调用queryUserById方法,再次查询id为1的用户信息
        User user2 = mapper2.queryUserById(1);
        // 打印用户信息
        System.out.println(user2);
        
        // 判断两个用户信息是否相等
        System.out.println(user==user2);
        // 关闭第二个sqlSession对象
        sqlSession2.close();
    }
    
}

3.测试结果

Mybatis基础学习之一级缓存和二级缓存的简单使用_第8张图片

结果两次都成功查询到了id为1的用户信息!

分析

第一次查询完id为1的用户信息后,本地缓存关闭,将数据存入二级缓存;再次查询此用户信息时,不用执行预编译SQL,查询二级缓存,直接取出该用户信息;但仔细观察后发现,"user==user2"的结果却为false了,是因为从二级缓存中取出该用户信息,内存地址发生了变化,因此结果为false

14.5.6 二级缓存使用总结

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有数据都会先放在一级缓存中
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中

好了,今天的有关 一级缓存和二级缓存的简单使用 的学习就到此结束啦。欢迎小伙伴们积极学习和讨论,喜欢的可以给蜗牛君点个关注,顺便来个一键三连。我们下期见,拜拜啦!


参考视频链接:【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

你可能感兴趣的:(Mybatis基础学习,mybatis,一级缓存,二级缓存,mysql,java)