什么是缓存?
缓存就是存储数据的一个地方(称作:Cache),当程序要读取数据时,会首先从缓存中获取,有则直接返回,否则从其他存储设备中获取,缓存最重要的一点就是从其内部获取数据的速度是非常快的,通过缓存可以加快数据的访问速度。比如我们从db中获取数据,中间需要经过网络传输耗时,db server从磁盘读取数据耗时等,如果这些数据直接放在jvm对应的内存中,访问是不是会快很多。
mybatis中的缓存
通常情况下mybatis会访问数据库获取数据,中间涉及到网络通信,数据库从磁盘中读取数据,然后将数据返回给mybatis,总的来说耗时还是挺长的,mybatis为了加快数据查询的速度,在其内部引入了缓存来加快数据的查询速度。
mybatis中分为一级缓存和二级缓存。
一级缓存是SqlSession级别的缓存,在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
下面我们详细说一下一级缓存和二级缓存的各种用法和注意点。
一级缓存
一级缓存是SqlSession级别的缓存,每个SqlSession都有自己单独的一级缓存,多个SqlSession之间的一级缓存是相互隔离的,互不影响,mybatis中一级缓存是默认自动开启的。
一级缓存工作原理:在同一个SqlSession中去多次去执行同样的查询,每次执行的时候会先到一级缓存中查找,如果缓存中有就直接返回,如果一级缓存中没有相关数据,mybatis就会去db中进行查找,然后将查找到的数据放入一级缓存中,第二次执行同样的查询的时候,会发现缓存中已经存在了,会直接返回。一级缓存的存储介质是内存,是用一个HashMap来存储数据的,所以访问速度是非常快的。
一级缓存案例
案例sql脚本
DROP DATABASE IF EXISTS `javacode2018`;
CREATE DATABASE `javacode2018`;
USE `javacode2018`;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
id int AUTO_INCREMENT PRIMARY KEY COMMENT '用户id',
name VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户名',
age SMALLINT NOT NULL DEFAULT 1 COMMENT '年龄'
) COMMENT '用户表';
INSERT INTO t_user VALUES (1,'路人甲Java',30),(2,'张学友',50),(3,'刘德华',50);
下面是查询用户信息,返回一个list
<select id="getList1" resultType="com.javacode2018.chat05.demo9.model.UserModel" parameterType="map">
SELECT id,name,age FROM t_user
<where>
<if test="id!=null">
AND id = #{id}
</if>
<if test="name!=null and name.toString()!=''">
AND name = #{name}
</if>
<if test="age!=null">
AND age = #{age}
</if>
</where>
</select>
对应的mapper接口方法
List<UserModel> getList1(Map<String, Object> paramMap);
测试用例
com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest1
/**
* 一级缓存测试
*
* @throws IOException
*/
@Test
public void level1CacheTest1() throws IOException {
String mybatisConfig = "demo9/mybatis-config.xml";
this.before(mybatisConfig);
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//第一次查询
List<UserModel> userModelList1 = mapper.getList1(null);
log.info("{}", userModelList1);
//第二次查询
List<UserModel> userModelList2 = mapper.getList1(null);
log.info("{}", userModelList2);
log.info("{}", userModelList1 == userModelList2);
}
}
上面的代码在同一个SqlSession中去执行了2次获取用户列表信息,2次查询结果分别放在userModelList1
和userModelList2
,最终代码中也会判断这两个集合是否相等,下面我们运行一下看看会访问几次db?
运行输出
01:15.312 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Preparing: SELECT id,name,age FROM t_user
01:15.340 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters:
01:15.364 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <== Total: 3
01:15.364 [main] INFO c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=张学友, age=50), UserModel(id=3, name=刘德华, age=50)]
01:15.367 [main] INFO c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=张学友, age=50), UserModel(id=3, name=刘德华, age=50)]
01:15.367 [main] INFO c.j.chat05.demo9.Demo9Test - true
从输出中可以看出看到,sql只输出了一次,说明第一次会访问数据库,第二次直接从缓存中获取的,最后输出了一个true
,也说明两次返回结果是同一个对象,第二次直接从缓存中获取数据的,加快了查询的速度。
清空一级缓存的3种方式
同一个SqlSession中查询同样的数据,mybatis默认会从一级缓存中获取,如果缓存中没有,才会访问db,那么我们如何去情况一级缓存呢,强制让查询去访问db呢?
让一级缓存失效有3种方式:
-
SqlSession中执行增、删、改操作,此时sqlsession会自动清理其内部的一级缓存
-
调用SqlSession中的clearCache方法清理其内部的一级缓存
-
设置Mapper xml中select元素的flushCache属性值为true,那么执行查询的时候会先清空一级缓存中的所有数据,然后去db中获取数据
上面方式任何一种都会让当前SqlSession中的以及缓存失效,进而去db中获取数据,下面我们来分别演示这3种情况。
方式1:增删改让一级缓存失效
当执行增删改操时,mybatis会将当前SqlSession一级缓存中的所有数据都清除。
案例代码:
com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest2
/**
* 增删改使一级缓存失效
*
* @throws IOException
*/
@Test
public void level1CacheTest2() throws IOException {
String mybatisConfig = "demo9/mybatis-config.xml";
this.before(mybatisConfig);
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//第一次查询
List<UserModel> userModelList1 = mapper.getList1(null);
log.info("{}", userModelList1);
//新增一条数据
mapper.insert1(UserModel.builder().id(100).name("路人").age(30).build());
//第二次查询
List<UserModel> userModelList2 = mapper.getList1(null);
log.info("{}", userModelList2);
log.info("{}", userModelList1 == userModelList2);
}
}
上面同一个SqlSession中执行了3个操作,同样的查询执行了2次,2次查询中间夹了一个插入操作。
运行输出
21:55.097 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Preparing: SELECT id,name,age FROM t_user
21:55.135 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters:
21:55.159 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <== Total: 3
21:55.159 [main] INFO c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=张学友, age=50), UserModel(id=3, name=刘德华, age=50)]
21:55.161 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==> Preparing: INSERT INTO t_user (id,name,age) VALUES (?,?,?)
21:55.162 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==> Parameters: 100(Integer), 路人(String), 30(Integer)
21:55.165 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - <== Updates: 1
21:55.166 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Preparing: SELECT id,name,age FROM t_user
21:55.166 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters:
21:55.167 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <== Total: