一级缓存
在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.缓存何时被清空
- 当SqlSession调用了close方法时,会话会被关闭,同时它的缓存也会被清空,并且不再可用。
- SqlSession进行了增、删、改操作,其缓存会被清空。如果对数据库进行数据更改,那么缓存必须被清空,否则就会形成脏数据。
- SqlSession调用了clearCache方法,表示手动清空缓存。
2. 如何判断是“相同”的操作
之前提到:“用当前SqlSession进行相同查询时,缓存会生效”。那么MyBatis是如何判断查询相同呢。
如果满足以下所有条件,则认为查询相同
- 最终传递给java.sql.preparedStatement对象的Sql语句字符串。
- 传入的StatementId
- 传入给Statement的参数
- 查询范围
二级缓存
之前提到,一级缓存的作用域是单个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中默认没有开启二级缓存,需要我们手动开启:
在我们需要手动开启的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
可以看到,分割线下方显示缓存被命中了。
对于二级缓存,需要注意的有:
- mapper的增、删、改操作会刷新缓存。
- SqlSession需要调用commit()或是close(),数据才被存入二级缓存。