MyBatis一级缓存和二级缓存

MyBatis有多种提高查询效率的方法,比如说懒加载、缓存。MyBatis为我们提供了一级缓存和二级缓存。其中一级缓存是默认开启的,二级缓存需要我们自己去Mapper文件里配置。

一级缓存

一级缓存是SqlSession级别的,而且是默认开启的(但是这里需要注意的是MyBatis和Spring整合之后,一级缓存就会失效,每调用一次Mapper里的方法,就会重新创建一个SqlSession)。也就是说在同一个SqlSession里,相同的查询语句,第一次查询的结果会被缓存(PerpetualCache,缓存默认的实现类,包括一级缓存和二级缓存),后面的查询会直接从缓存里取数据。一级缓存里存的是上一次查询的对象,两个对象是完全相同的。

image-20200512141519728

image-20200512153214314

代码示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 一级缓存默认开启,sqlsession级别
SqlSession sqlSession = sqlSessionFactory.openSession();
MessageDao messageDao1 = sqlSession.getMapper(MessageDao.class);
MessageDao messageDao2 = sqlSession.getMapper(MessageDao.class);
// 第一次查询,会发出sql语句,并将查询的结果放入缓存中
Message message1 = messageDao1.selectByPrimaryKey(2);
Message message2 = messageDao2.selectByPrimaryKey(2);
log.info("第一次查询结果 message1 = {}", message1);
log.info("第二次查询结果 message2 = {}", message2);
sqlSession.close();
SqlSession sqlSession1 = sqlSessionFactory.openSession();
MessageDao messageDao3 = sqlSession1.getMapper(MessageDao.class);
Message message3 = messageDao3.selectByPrimaryKey(2);
log.info("第三次查询结果 message3 = {}", message3);

运行结果1

image-20200512151447150

从运行结果可以看出,同一个SqlSession里,第一次查询的时候会把缓存数据,第二次查询的时候并没有去数据库里查询。不同的SqlSession之间缓存是不共享的。

代码示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 一级缓存默认开启,sqlsession级别
SqlSession sqlSession = sqlSessionFactory.openSession();
MessageDao messageDao1 = sqlSession.getMapper(MessageDao.class);
MessageDao messageDao2 = sqlSession.getMapper(MessageDao.class);
// 第一次查询,会发出sql语句,并将查询的结果放入缓存中
Message message1 = messageDao1.selectByPrimaryKey(2);
log.info("第一次查询结果 message1 = {}", message1);
messageDao1.updateByPrimaryKey(2);
log.info("更新了数据");
Message message2 = messageDao2.selectByPrimaryKey(2);
log.info("第二次查询结果 message2 = {}", message2);
sqlSession.close();
sqlSession = sqlSessionFactory.openSession();
MessageDao messageDao3 = sqlSession.getMapper(MessageDao.class);
Message message3 = messageDao3.selectByPrimaryKey(2);
log.info("第三次查询结果 message3 = {}", message3);

运行结果2

image-20200512153412734

二级缓存

二级缓存默认是不开启的,并且二级缓存是基于 mapper 中的 namespace 的,也就是说就算是不同的sqlSession,如果namespace相同,那么还是可以共享缓存的。

image-20200512154757807

开启二级缓存

  1. 设置 cacheEnable 属性值为 true

  2. 在对应的mapper xml文件里添加 <cache />标签,PerpetualCache 是mybatis提供的默认缓存实现类,内部数据结构是HashMap。

    1
    2
    > <cache type="org.apache.ibatis.cache.impl.PerpetualCache"></cache>
    >

代码示例

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
// 二级缓存默认不开启,需要去手动开启
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
MessageDao messageDao1 = sqlSession1.getMapper(MessageDao.class);
MessageDao messageDao2 = sqlSession2.getMapper(MessageDao.class);
MessageDao messageDao3 = sqlSession3.getMapper(MessageDao.class);

// 第一次查询,会发出sql语句,并将查询的结果放入缓存中
Message message1 = messageDao1.selectByPrimaryKey(2);
// 需要提交之后二级缓存才会生效
log.info("message1 = {}", message1);
sqlSession1.commit();
Message message2 = messageDao2.selectByPrimaryKey(2);
log.info("message2 = {}", message2);
Message message3 = messageDao3.selectByPrimaryKey(2);
log.info("message3 = {}", message3);
// sqlSession1.close();

// 修改数据,如果这里的 flushCache 属性值为 false ,那么会造成脏读,默认这个属性是为true的
// 一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可
messageDao3.updateByPrimaryKey(2);
sqlSession3.commit();
// // 第二次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句
message2 = messageDao2.selectByPrimaryKey(2);
log.info("message2 = {}", message2);

运行结果

image-20201201160323170

从上面的运行结果可以看出,二级缓存开启之后,messageDao2,messageDao3这两次查询都是从缓存里面去查找数据的,缓存命中率分别为0.5和0.66666666。sqlSession3修改数据之后并且commit,可以发现messageDao2并没有从缓存里面取出数据,而是从数据库里获取数据。

如何关闭一级缓存

  1. 设置localCacheScopeSTATEMENT 级别,默认是SESSION级别。
  2. 设置 flushCachetrue

参考资料

  1. https://zhuanlan.zhihu.com/p/37260121
  2. https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
  3. https://blog.csdn.net/ctwy291314/article/details/81938882
显示评论