MyBatis一级缓存与二级缓存简介

一级缓存

在MyBatis中,默认开启了一级缓存,它的作用域仅限在单个SqlSession对象中。
例如,在使用一个SqlSession进行一次查询后,查询结果会缓存在当前SqlSession中,再次用当前SqlSession进行相同查询时,只需在缓存中查找数据而不用到数据库中。

这里我们可以使用log4j打印数据库查询日志,来观察MyBatis对数据库访问的情况。

以下是对User关系表进行两次全查的实例:

public class Test6 {
    public static void main(String[] args) throws IOException {
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        SqlSession sqlSession = factory.openSession();
        
        sqlSession.selectList("com.kkb.dao.UserDao.getAll"); 
        //使用sqlSession进行第一次查询
        System.out.println("===========分割线============");
        sqlSession.selectList("com.kkb.dao.UserDao.getAll"); 
        //使用相同的sqlSession进行第二次查询
        sqlSession.close();
    }
}

这个程序的期望显示是,如果两次查询都访问数据库,那么分割线上下都会打印访问记录,而如果只是第一次查询访问了数据库,那么只有分割线上方出现访问记录。

运行后,log4j打印结果:

2021-01-18 11:30:38,722 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==>  Preparing: select * from User 
 2021-01-18 11:30:38,749 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==> Parameters: 
 2021-01-18 11:30:38,777 [main] DEBUG [com.kkb.dao.UserDao.getAll] - <==      Total: 5
 =======================

Process finished with exit code 0

可以看到在分割线下没有记录,表示第二次查询没有访问到数据库,说明数据成功缓存。

缓存何时被清空

  1. SqlSessio调用了close方法。
  2. SqlSession进行了增、删、改操作。
  3. SqlSession调用了clearCache方法。

如何判断是“相同”的操作

之前提到:“用当前SqlSession进行相同查询时,缓存会生效”。那么MyBatis是如何判断查询相同呢。
如果满足以下所有条件,则认为查询相同

  1. 最终传递给java.sql.preparedStatement对象的Sql语句字符串。
  2. 传入的StatementId
  3. 传入给Statement的参数
  4. 查询范围

二级缓存

之前提到,一级缓存的作用域是单个SqlSession对象,那么我们可以进行测试,更改上面的程序,使用两个SqlSession对象来进行相同的查询。

public class Test6 {
    public static void main(String[] args) throws IOException {
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        SqlSession sqlSession1 = factory.openSession();
        SqlSession sqlSession2 = factory.openSession();
        sqlSession1.selectList("com.kkb.dao.UserDao.getAll"); //使用对象 sqlSession1 进行第一次查询
        sqlSession1.close();
        System.out.println("===========分割线============");
        sqlSession2.selectList("com.kkb.dao.UserDao.getAll"); //使用对象 sqlSession2 进行第二次查询
        sqlSession2.close();
    }
}

运行结果:

2021-01-18 11:43:33,743 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==>  Preparing: select * from User 
 2021-01-18 11:43:33,771 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==> Parameters: 
 2021-01-18 11:43:33,802 [main] DEBUG [com.kkb.dao.UserDao.getAll] - <==      Total: 5
 ===========分割线============
2021-01-18 11:43:33,805 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==>  Preparing: select * from User 
 2021-01-18 11:43:33,805 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==> Parameters: 
 2021-01-18 11:43:33,807 [main] DEBUG [com.kkb.dao.UserDao.getAll] - <==      Total: 5
 
Process finished with exit code 0

可以看到,数据库被访问了两次,缓存并没有效果。

这是就需要二级缓存,二级缓存的作用域是单个mapper,即使在多个SqlSession中,只要他们的查询是相同的namespace,那么他们就会共享缓存。
MyBatis一级缓存与二级缓存简介
MyBatis中默认没有开启二级缓存,需要我们手动开启:

在我们需要手动开启的mapper对应的xml配置文件中添加以下配置(注意是在mapper标签内):

 <cache eviction="FIFO" 
 flushInterval="60000" 
 size="512" 
 readOnly="true"/> 

四个属性说明:

eviction:

表示缓存淘汰策略,常用取值可以为: LRU,最近最少使用策略,最不常用的缓存优先淘汰。 FIFO,先进先出策略,按照进入顺序依次退出。

flushInterval:

刷新时间间隔,单位毫秒。默认情况是,没有刷新间隔,即刷新只在调用语句时发生。

size:

表示缓存数目,即缓存的对象数,默认是1024。

readOnly:

表示是否只读,取值为true或false。只读的缓存,会给调用者返回实例本身,性能较高。而可读写的缓存,会给调用者返回实例的拷贝,性能较低,但是较为安全。

然后需要注意的是,映射的对象需要实现序列化接口,以便在缓存介质是硬盘或者远程存储器的情况下发挥作用。

接下来就可以进行测试,以下是使用两个SqlSession对象进行相同的查询:

public class Test6 {
    public static void main(String[] args) throws IOException {
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        SqlSession sqlSession1 = factory.openSession();
        SqlSession sqlSession2 = factory.openSession();
        sqlSession1.selectList("com.kkb.dao.UserDao.getAll"); //使用对象 sqlSession1 进行第一次查询
        sqlSession1.close();
        System.out.println("===========分割线============");
        sqlSession2.selectList("com.kkb.dao.UserDao.getAll"); //使用对象 sqlSession2 进行第二次查询
        sqlSession2.close();
    }
}

运行结果:

2021-01-18 12:10:34,960 [main] DEBUG [com.kkb.dao.UserDao] - Cache Hit Ratio [com.kkb.dao.UserDao]: 0.0
 2021-01-18 12:10:35,758 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==>  Preparing: select * from User 
 2021-01-18 12:10:35,791 [main] DEBUG [com.kkb.dao.UserDao.getAll] - ==> Parameters: 
 2021-01-18 12:10:35,820 [main] DEBUG [com.kkb.dao.UserDao.getAll] - <==      Total: 5
 ===========分割线============
2021-01-18 12:10:35,823 [main] DEBUG [com.kkb.dao.UserDao] - Cache Hit Ratio [com.kkb.dao.UserDao]: 0.5

可以看到,分割线下方显示缓存被命中了。

对于二级缓存,需要注意的有:

  1. mapper的增、删、改操作会刷新缓存。
  2. SqlSession需要调用commit()或是close(),数据才被存入二级缓存。
上一篇:bzoj4036 [HAOI2015]按位或


下一篇:kmp字符串匹配