文章目录
一、什么是缓存
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查询数据的顺序是:
二级缓存 --> 一级缓存 --> 数据库