Mybatis缓存简介

缓存就是内存中的数据,常常来自于对数据库查询结果的保存,使用缓存,我们可以避免频繁的与数据库进行交互,从而提高响应速度。Mybatis中的缓存分为一级缓存和二级缓存:

  1. 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是相互不影响的。
  2. 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

一级缓存

mybatis中一级缓存默认是开启的。

测试一级缓存

下面我们进行测试一级缓存。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CacheTest {
private SqlSession sqlSession;
private UserDao userDao;
@Before
public void createSqlSession() throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession(true);
userDao = sqlSession.getMapper(UserDao.class);
}

/**
* 测试一级缓存
*/
@Test
public void firstLevelCache(){
//第一次查询id为1的数据
User user1 = userDao.selectUserByUserId(1);
//第二次查询id为1的数据
User user2 = userDao.selectUserByUserId(1);
//判断查询结果是否是同一个User对象
System.out.println(user1==user2);
}
}

执行结果为:

我们在执行完第一次查询后,执行下更新操作,

1
2
3
4
5
6
7
8
9
10
11
12
13
 @Test
public void firstLevelCache(){
//第一次查询
User user1 = userDao.selectUserByUserId(1);
//更新操作
User user=new User();
user.setId(1);
user.setUsername("哈哈");
userDao.updateUserByUserId(user);
//第二次查询
User user2 = userDao.selectUserByUserId(1);
System.out.println(user1==user2);
}

执行结果:

可以看到如果我们在第一次查询之后执行了更新操作,那么第二次查询也不走缓存,直接走新sql。

刚刚我们更新的是我们第一次查询的对象,假设我们更新的是另一个id=2的记录,那么再次查询id=1的记录,会走缓存吗?答案是:不会。

实战结果如下:

执行完更新操作后,缓存将会被清除,所以往后的查询都需要执行新的sql。

总结

1,第一次查询id为1的数据,先去缓存中查找,如果不存在,则去数据库中查询用户信息,将用户信息存入一级缓存中。

2,如果中间sqlSession执行了commit操作(更新、插入、删除),则会清空sqlSession一级缓存中的数据,这样做的目的是让缓存中存最新的数据,避免脏读。

3,第二次查询id为1的数据,如果一级缓存中有,则直接返回。

用图示的形式表达如下:

源码

一级缓存到底是个什么东西呢?一级级分析源码,我们会发现底层是一个HashMap,如下:

二级缓存

简介

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

和一级缓存默认开启不一样,二级缓存需要我们手动开启。

开启二级缓存

第一步,在mybatis主配置文件SqlMapConfig.xml中添加配置:

1
2
3
4
 <settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>

第二步,在UserMapper.xml中添加<cache></cache>空标签即可。不进行任何配置,表示使用mybatis的默认缓存的实现。我们也可以自定义缓存的实现。

第三步,在pojo类中要实现序列化接口Serializable。因为二级缓存存储介质多种多样,不一定只是在内存中,有可能在硬盘中,如果我们要取这个缓存的话,就需要反序列化了,所以需要实现序列化接口。

测试

测试二级缓存与sqlSession无关

我们生产两个sqlSession,对应两次查询,第一次查询后将sqlSession关闭,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 /**
* 测试二级缓存
* @throws IOException
*/
@Test
public void secondLevelCache() throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(resourceAsStream);
//根据sqlSessionFactory产生sqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();

UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);

//第一次查询
User user1 = userDao1.selectUserByUserId(1);
System.out.println(user1);
sqlSession1.close();

//第二次查询
User user2 = userDao2.selectUserByUserId(1);
System.out.println(user2);
sqlSession2.close();

}

运行结果:

可以看到即使第一个sqlSession关闭了,第二个sqlSession仍然不发出查询语句。

测试二级缓存读取最新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 /**
* 测试二级缓存
* @throws IOException
*/
@Test
public void secondLevelCache() throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(resourceAsStream);
//根据sqlSessionFactory产生sqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();

UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
UserDao userDao3 = sqlSession3.getMapper(UserDao.class);

//第一次查询
User user1 = userDao1.selectUserByUserId(1);
System.out.println(user1);
sqlSession1.close();

//执行更新操作,commit
User user=new User();
user.setId(2);
user.setUsername("李哲");
user.setPassword("333");
userDao3.updateUserByUserId(user);
sqlSession3.commit();

//第二次查询
User user2 = userDao2.selectUserByUserId(1);
System.out.println(user2);
sqlSession2.close();

}

运行结果:

可以看出,执行完更新操作后,二级缓存中的数据会被清空,为防止脏读,第二次查询依然会发出新SQL。

源码

mybatis缓存demo

总结

我们了解了mybatis的一级缓存与二级缓存的概念,通过代码测试验证了一级缓存与二级缓存的存在,对mybatis的一级缓存与二级缓存有了初步的认识。