MyBatis学习(6)缓存机制(一级缓存、二级缓存)

一、什么是缓存

1. 缓存的概念

缓存是存在于内存中的临时数据。

2. 为什么使用缓存

最根本的原因是速度不匹配。由于数据库太慢,把常用的业务数据放到内存中缓存起来,通过缓存策略来减少数据库的查询次数,从而加快访问速度。

3. 缓存适用于什么样的数据

缓存适用于经常查询并且不会经常修改的数据,数据的正确与对最终的结果影响不是特别大。

二、Mybatis中的缓存

MyBatis系统中默认定义了 两级缓存,分别是 一级缓存二级缓存

缓存结构示意图:
在这里插入图片描述
(此图片来源于网络)

如上图所示,当客户端打开一个会话的时候,一个 SqlSession 对象会使用一个 Executor对象来完成会话操作。
  如果用户在全局配置文件中配置了 “cacheEnabled=true”,那么MyBatis 在为 SqlSession 对象创建 Executor 对象时,会对 Executor 对象加上一个装饰者 CachingExecutor,这时 SqlSession 使用 CachingExecutor 对象来完成操作请求。
  CachingExecutor 对于查询请求会先判断该查询请求在 Application 级别的二级缓存中是否有缓存的数据。
  如果有需要的数据,则直接返回缓存结果;
  如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后 CachingExecutor 会将真正 Executor 返回的查询结果放置到缓存中,然后再返回给用户。

1. 一级缓存

一级缓存 是 Mybatis 中 SqlSession 对象级别的缓存,也称为本地缓存。

当执行查询操作之后,查询的结果会同时存入SqlSession提供的一块区域中。co从下图的源码可以看出,该区域的结构是一个Map,当下一次查询同样的数据,mybatis会先从该区域中查询是否有数据,有的话直接从该区域拿出数据。当SqlSession对象消失以后,缓存也消失了。
在这里插入图片描述

2. 二级缓存

二级缓存 是 Mybatis 中 SqlSessionFactory 对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。

  • 默认情况下,只有一级缓存开启。
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 一级缓存是SqlSession级别的缓存,只要SqlSession没有flush或close,它就存在。

三、准备开发环境

1. 搭建Maven工程,创建用户表

在这里插入图片描述

2. 编写配置文件

jdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_study?characterEncoding=utf8
jdbc.username=root
jdbc.password=root

Mybatis-config.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 -->
    <properties resource="jdbcConfig.properties"></properties>
    
    <!-- 使用typeAliases配置别名,只能配置pojo中的别名-->
    <typeAliases>
        <package name="com.zxy.pojo"/>
    </typeAliases>
    <!-- 配置环境 -->
    <environments default="mysql">
        <!-- 配置mysql环境 -->
        <environment id="mysql">
            <!-- 配置事务 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接池 -->
            <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>
        <package name="com.zxy.dao"/>
    </mappers>
</configuration>

四、Mybatis中的一级缓存

1. 用户实体类

User

public class User implements Serializable {
    private Integer id; // 用户Id
    private String username; // 用户姓名
    private String sex; // 用户性别
    private Date birthday; // 用户生日
    private String address; // 用户地址

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

2. 用户持久层dao接口

IUserDao

public interface IUserDao {

    /**
     * 根据id 查询用户信息
     *
     * @param userId
     * @return
     */
    User findById(Integer userId);
    
}

3. 用户持久层映射文件

IUserDao.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="com.zxy.dao.IUserDao">
    
    <!-- 根据id查询用户信息 -->
    <select id="findById" parameterType="int" resultType="com.zxy.pojo.User" >
		select
		     id as userId,
             username as userName,
             sex as userSex,
             birthday as userBirthday,
             address as userAddress
		from
		    user
		where
		    id = #{uid}
	</select>
</mapper>

4. 测试类 (测试一级缓存)

FirstLevelCacheTest

public class FirstLevelCacheTest {
    private InputStream input;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;

    /**
     * 初始化操作
     *
     * @throws Exception
     */
    @Before // 用于在测试方法执行之前执行
    public void init() throws Exception {
        // 1.读取配置文件
        input = Resources.getResourceAsStream("Mybatis-config.xml");
        // 2.获取SqlSessionFactory工厂
        factory = new SqlSessionFactoryBuilder().build(input);
        // 3.获取SqlSession 对象
        session = factory.openSession();
        // 4.获取dao接口的代理对象
        userDao = session.getMapper(IUserDao.class);
    }


    @After // 用于在测试方法执行之后执行
    public void destroy() throws Exception {
        // 提交事务
        session.commit();
        // 释放资源
        session.close();
        input.close();
    }

    /**
     * 测试Mybatis中的一级缓存
     */
    @Test
    public void testFirstLevelCache() {
        User user1 = userDao.findById(1);
        System.out.println(user1);
        
        User user2 = userDao.findById(1);
        System.out.println(user2);

        System.out.println(user1 == user2);
    }
}

测试结果:
在这里插入图片描述
在这里插入图片描述

虽然在上面的代码中我们执行了两次查询方法,但最后只执行了一次 select 查询语句。因为一级缓存的存在,导致第二次查询 id 为1的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询数据。

注意:

一级缓存是SqlSession范围的缓存,当调用SqlSession的添加,删除,修改,commit(),close()等方法时,就会清空一级缓存,缓存失效。

   /**
     * 测试一级缓存清楚操作
     */
    @Test
    public void testFirstLevelCache() {
    	// 第一次发起请求,查询id为1的用户
        User user1 = userDao.findById(1);
        System.out.println(user1);

        // 清楚缓存
        session.clearCache();

        userDao = session.getMapper(IUserDao.class);
		// 第二次发起请求,查询id为1的用户
        User user2 = userDao.findById(1);
        System.out.println(user2);

        System.out.println(user1 == user2);
    }

在这里插入图片描述
在这里插入图片描述
从上述图中的结果可以看出,执行了两次select查询语句,最后的结果也是false。

因此得出这样的结论:SqlSession 对象消失的时候,一级缓存也消失了

对用户信息执行更新操作

用于持久层接口增加更新方法

	/**
     * 更新用户信息
     * @param user
     */
    void updateUser(User user);

用户持久层映射文件增加更新操作的sql语句

	<!-- 更新用户信息 -->
    <update id="updateUser" parameterType="User">
        update user set username = #{username},address = #{address} where id = #{id}
    </update>
	/**
     * 测试缓存的同步
     */
    @Test
    public void testClearCache() {
        // 根据id 查询用户信息
        User user1 = userDao.findById(8);
        System.out.println(user1);

        // 更新用户信息
        user1.setUsername("扬帆向海");
        user1.setAddress("西安市长安区");
        userDao.updateUser(user1);

        // 再次查询id为 8 的用户
        User user2 = userDao.findById(8);
        System.out.println(user2);

        System.out.println(user1 == user2);
    }

}

在这里插入图片描述
在这里插入图片描述
总结:

如果中间 sqlSession 去执行 commit 操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,又重新执行了sql 语句,从数据库进行了查询操作。这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

五、Mybatis中的二级缓存

二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于 sqlSession的,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。

1. 在Mybatis-config.xml 文件中开启二级缓存

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

注意:因为 cacheEnabled 的值默认为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。

2. 映射配置文件中开启二级缓存

<!-- 开启User支持二级缓存  -->
    <cache></cache>

在这里插入图片描述

3. 配置statement上面的useCache属性

将UserDao.xml映射文件中的标签中设置useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。

在这里插入图片描述

public class SecondLevelCacheTest {
    private InputStream input;
    private SqlSessionFactory factory;

    /**
     * 初始化操作
     *
     * @throws Exception
     */
    @Before // 用于在测试方法执行之前执行
    public void init() throws Exception {
        // 1.读取配置文件
        input = Resources.getResourceAsStream("Mybatis-config.xml");
        // 2.获取SqlSessionFactory工厂
        factory = new SqlSessionFactoryBuilder().build(input);
    }

    @After // 用于在测试方法执行之后执行
    public void destroy() throws Exception {
        input.close();
    }

    /**
     * 测试二级缓存
     * 
     */
    @Test
    public void testSecondLevelCache() {
        SqlSession sqlSession1 = factory.openSession();
        IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
        // 第一次发起请求,查询id为1的用户
        User user1 = userDao1.findById(1);
        System.out.println(user1);
        // 关闭一级缓存,将sqlsession中的数据写到二级缓存区域
        sqlSession1.close();

        SqlSession sqlSession2 = factory.openSession();
        IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
        // 第二次发起请求,查询id为1的用户
        User user2 = userDao2.findById(1);
        System.out.println(user2);
        sqlSession2.close();

        System.out.println(user1 == user2);
    }
}

在这里插入图片描述
测试方法中在第一次查询结束后关闭了SqlSession。第二次查询的结果是从缓存中直接获取的数据。这就证明了二级缓存比一级缓存的范围大,只要是同一个mapper,都可以从二级缓存中得到数据。

注意:
在这里插入图片描述
最后的输出结果是false

原因是 二级缓存中存的是数据,而不是对象

六、一级缓存和二级缓存的使用顺序

如果你的 MyBatis 使用了二级缓存,并且你的 Mapper映射文件和 select 语句也配置了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:

           二级缓存  --> 一级缓存  --> 数据库
发布了73 篇原创文章 · 获赞 796 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_43570367/article/details/103321205