MyBatis——关于一级缓存 & 二级缓存的案例详解
Posted 张起灵-小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis——关于一级缓存 & 二级缓存的案例详解相关的知识,希望对你有一定的参考价值。
文章目录:
3.4.3 insert、update、delete操作会刷新缓存
4.2 dao接口、mapper文件、myabtis配置文件
1.写在前面
首先,我们来聊聊什么是缓存Cache?
说到数据库啊,无论是mysql、Sql Server、Oracle、Redis等等,它们当中都是有缓存机制的,那么到底什么是缓存呢?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(相当于从内存中拷贝了一块高速内存区,存放临时数据)中,用户去查询数据就不用从磁盘上(关系型数据库文件中)查询了,只需要去缓存中查询有无即可,从而提高了查询效率,解决了高并发系统的性能问题。
那么问什么要使用缓存呢?
- 目的是为了减少与数据库的交互次数,减少系统开销,提高系统的执行效率。
什么样的数据可以使用缓存呢?
- 经常查询,并且不经常改变的数据。
2.关于MyBatis中的缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率。
我们也可以在官方文档中看到有关缓存的定义说明
在MyBatis框架中默认定义了两级缓存:一级缓存和二级缓存。
- 默认情况下,MyBatis只开启了一级缓存(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存。
3.一级缓存案例详解
一级缓存也叫本地缓存:SqlSeesion。
- 与数据库同一次会话期间查询到的数据会放在本地缓存中(也就是同一个SqlSession对象)。
- 以后如果需要获取相同的数据,就可以直接在缓存中拿,没有必须再去查询数据库了。
- 一级缓存是默认开启的,只在一次SqlSession会话中有效,也就是从当前SqlSession拿到连接到close关闭连接这个区间段。
3.1 首先写一个实体Bean
头三个注解是lombok中的,用来自动生成get/set方法、toString、无参/有参构造方法、hashCode、equals这些方法。
pom文件中的依赖分别添加了:mybatis、mysql驱动、lombok、junit
package com.szh.mybatis.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
}
3.2 dao接口、对应的mapper映射文件
package com.szh.mybatis.mapper;
import com.szh.mybatis.bean.User;
import org.apache.ibatis.annotations.Param;
/**
*
*/
public interface UserMapper {
/**
* 根据id查询用户信息
* @param id
* @return
*/
User selectUserById(@Param("uid") Integer id);
/**
* 修改某个用户的信息
* @param user
* @return
*/
int updateUser(User user);
}
<?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.szh.mybatis.mapper.UserMapper">
<!-- 使用insert、update、delete、select标签编写sql语句 -->
<sql id="userInfo">
id, name, age
</sql>
<sql id="userTable">
user
</sql>
<select id="selectUserById" resultType="com.szh.mybatis.bean.User">
select <include refid="userInfo"/>
from <include refid="userTable"/>
where id=#{uid}
</select>
<update id="updateUser" parameterType="com.szh.mybatis.bean.User">
update <include refid="userTable"/>
set name=#{name},age=#{age}
where id=#{id}
</update>
</mapper>
3.3 mybatis配置文件、工具类
这个可以参考这篇文章,这两个代码是一样的。
https://blog.csdn.net/weixin_43823808/article/details/118111961
3.4 测试方法
3.4.1 同一个会话对象查询同一个数据
@Test
public void test01() {
SqlSession session=MyBatisUtil.getSqlSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1001);
System.out.println(user);
System.out.println("==============================");
User user1 = userMapper.selectUserById(1001);
System.out.println(user);
session.close();
}
可以看到我们new第一个对象时,它去数据库中查询了一次,执行了一次select语句;而我们new第二个对象时,这个时候,它就没有再进数据库,没有执行select语句,因为面对同一个SqlSeesion会话对象,第一次查询之后,这条数据就已经存在于缓存中了,所以第二次查询直接去缓存中拿就可以了。
3.4.2 同一个会话对象查询两个不同的数据
@Test
public void test02() {
SqlSession session=MyBatisUtil.getSqlSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1001);
System.out.println(user);
System.out.println("==============================");
User user1 = userMapper.selectUserById(1002);
System.out.println(user);
session.close();
}
这个肯定是执行两次select语句了,因为虽然是同一个SqlSession会话对象,但这查询的是两条不同的数据,所以自然会执行两次sql语句,可以理解为缓存中此时有1001、1002这两条数据了。
3.4.3 insert、update、delete操作会刷新缓存
@Test
public void test03() {
SqlSession session=MyBatisUtil.getSqlSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1001);
System.out.println(user);
int rows=userMapper.updateUser(new User(1002,"小宋",21));
System.out.println("==============================");
User user1 = userMapper.selectUserById(1001);
System.out.println(user);
session.close();
}
这里虽然是面对同一个SqlSession会话对象,然后查询的也是同一个数据,但是中间执行了一次update修改操作,所以这时候会刷新缓存,当你第二次再查询1001数据时,缓存已经被刷新了,就没有1001这条数据了,所以两次查询都会去数据库中查找。
3.4.4 手动清理缓存
@Test
public void test04() {
SqlSession session=MyBatisUtil.getSqlSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1001);
System.out.println(user);
session.clearCache();//手动清理缓存
System.out.println("==============================");
User user1 = userMapper.selectUserById(1001);
System.out.println(user);
session.close();
}
这个就不再解释了,因为你都手动刷新缓存了,所以select两次,肯定两次都会去数据库中查找,因为缓存被刷新了。
4.二级缓存案例讲解
二级缓存也叫全局缓存,因为之前一级缓存的作用域太低了,所以就诞生了二级缓存。
二级缓存是基于namespace级别的缓存,一个命名空间(namespace)对应一个二级缓存。
工作机制:
- 一个会话对象(SqlSession)查询一条数据,这个数据就会被存放在当前会话的一级缓存中。
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;只有当前会话关闭或者提交了,一级缓存中的数据才会被保存到二级缓存中。那么此时新的会话查询数据,就可以从二级缓存中获取数据。
- 不同的mapper具有不同的namespace,查出的数据自然也会放在自己对应的缓存中。
- 只要开启了二级缓存,在同一个mapper下就有效。
4.1 开启二级缓存之后,实体类需要实现序列化
package com.szh.mybatis.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
@Serial
private static final long serialVersionUID = 8349440997686871823L;
private Integer id;
private String name;
private Integer age;
}
4.2 dao接口、mapper文件、myabtis配置文件
dao接口和上面一级缓存案例的一样。
mapper文件中添加了一个 <cache/> 标签,其他都一样。
<cache/>
mybatis配置文件中多了这一行。
<!-- 显式的开启缓存-->
<setting name="cacheEnabled" value="true"/>
4.3 测试方法
4.3.1 一级缓存面对两个SqlSession会话对象
先把mapper文件中的 <cache/> 标签注释掉。
@Test
public void test05() {
SqlSession session=MyBatisUtil.getSqlSession();
SqlSession session2=MyBatisUtil.getSqlSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
UserMapper userMapper2=session2.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1001);
System.out.println(user);
session.close();
System.out.println("==============================");
User user2 = userMapper2.selectUserById(1001);
System.out.println(user2);
session2.close();
}
可以看到,我们在测试方法中创建了两个会话对象(SqlSeesion),那么在第一个会话对象close关闭之后,一级缓存就没了,所以第二个会话对象肯定还会去数据库中查找数据啊。
4.3.2 二级缓存面对两个SqlSession会话对象
先把mapper文件中的 <cache/> 标签打开。测试方法和上面的一样。
我们来看下面的执行结果。
这个时候,我们开启了二级缓存,那就不一样了啊,你第一个会话对象select查询之后,执行close关闭了,此时二级缓存会发挥作用,它会将你一级缓存(对应第一个会话对象)中查询到的那个数据存放到二级缓存中,这个时候,第二个会话对象再执行select,就不用去数据库中查找了,因为这个数据已经存在在二级缓存中了,直接拿数据就可以了。所以自始至终,只用执行一次select语句。
5.MyBatis缓存原理
默认开启一级缓存,之后使用<cache/>开启二级缓存。测试。
@Test
public void test06() {
SqlSession session=MyBatisUtil.getSqlSession();
SqlSession session2=MyBatisUtil.getSqlSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
UserMapper userMapper2=session2.getMapper(UserMapper.class);
//查询1001,此时数据库缓存中什么都没有,查询1001,直接去数据库中查找,执行sql语句
User user = userMapper.selectUserById(1001);
System.out.println(user);
session.close(); //关闭第一个会话对象,此时1001数据会被转移到二级缓存中
System.out.println("==============================");
//查询1001,先去二级缓存中查找,有1001,直接从二级缓存中拿,不用再去数据库中查找,自然不会执行sql语句
User user2=userMapper2.selectUserById(1001);
System.out.println(user2);
//查询1002,二级缓存中没有、一级缓存中也没有,所以直接去数据库中查找,执行sql语句
User user3=userMapper2.selectUserById(1002);
System.out.println(user2);
System.out.println(user3);
//查询1002,先去二级缓存中查找,有1002,直接从二级缓存中拿,不用再去数据库中查找,自然不会执行sql语句
User user4=userMapper2.selectUserById(1002);
System.out.println(user4);
session2.close();
}
以上是关于MyBatis——关于一级缓存 & 二级缓存的案例详解的主要内容,如果未能解决你的问题,请参考以下文章