请先观察下面程序的执行结果:
package cn.jingpengchong.userinfo.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import cn.jingpengchong.userinfo.dao.IUserInfoDao;
import cn.jingpengchong.userinfo.vo.UserInfo;
public class Test {
public static void main(String[] args) {
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
IUserInfoDao userInfoDao = session.getMapper(IUserInfoDao.class);
List<UserInfo> list = userInfoDao.selectAll();
System.out.println(list.size());
list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果如下:
我们发现明明我们程序中查了两次数据库,但从日志输出来看实际上只访问了一次数据库,那么这是什么原因呢?答案就是:缓存。为了提升查询效率,提高用户体验,MyBatis提供了数据缓存支持,依据数据缓存的有效范围默认定义了一级缓存和二级缓存。第一次查询数据库后,Mybatis自动将结果放在了一级缓存中,当第二次的查询语句与第一次一样的时候,并且中间没有操作数据库中的数据且没有清除缓存时,Mybatis就会直接把缓存中的数据返回给用户。这样做的好处就是减少了对数据库的频繁访问对数据库造成的损害,并且提升了查询速度。
一级缓存为SqlSession级别的缓存,也称为本地缓存,默认是开启的,不能关闭。但是以下4种情况将会再次访问数据库:
这种情况严格意义上来讲并不算是一级缓存失效了,因为一级缓存是SqlSession级别的缓存,在该SqlSession外自然也就访问不到它的缓存了。例如,将上面程序中的try代码块中的代码做如下修改:
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
IUserInfoDao userInfoDao = session.getMapper(IUserInfoDao.class);
List<UserInfo> list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
session = factory.openSession();
userInfoDao = session.getMapper(IUserInfoDao.class);
list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
这种情况也不算是缓存失效了,缓存中的数据还在,只不过由于缓存中没有存储该语句查询出来的数据,因此第二次Mybatis还会去访问数据库。例如,将上面程序中的try代码块中的代码做如下修改:
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
IUserInfoDao userInfoDao = session.getMapper(IUserInfoDao.class);
List<UserInfo> list = userInfoDao.selectAll(20);
System.out.println(list.size());
list = userInfoDao.selectAll(25);
System.out.println(list.size());
session.close();
这种情况是缓存失效了,因为增删改都有一个flushCache属性并且默认值是true,如果执行了增删改操作就会刷新缓存。当然,如果是false的话就不会刷新缓存了,但是这种做法是错误的,会导致第二次查询到的数据不正确。例如,将上面程序中的try代码块中的代码做如下修改:
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
IUserInfoDao userInfoDao = session.getMapper(IUserInfoDao.class);
List<UserInfo> list = userInfoDao.selectAll();
System.out.println(list.size());
userInfoDao.delete("'%四%'");
list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
既然清除了缓存,那么之前的缓存当然是失效了。例如,将上面程序中的try代码块中的代码做如下修改:
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
IUserInfoDao userInfoDao = session.getMapper(IUserInfoDao.class);
List<UserInfo> list = userInfoDao.selectAll();
System.out.println(list.size());
session.clearCache();
list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
该级缓存为namespace级别的缓存,通过SqlSession查询数据,这些数据将会放到当前会话的一级缓存中;如果当前会话关闭,则一级缓存中的数据会被保存到二级缓存中,此后新的SqlSession将从二级缓存中查找数据。
二级缓存默认不开启,但如果使用二级缓存需要在每个XML映射文件中添加
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
IUserInfoDao userInfoDao = session.getMapper(IUserInfoDao.class);
List<UserInfo> list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
session = factory.openSession();
userInfoDao = session.getMapper(IUserInfoDao.class);
list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
运行程序,会发现尽管两次查询不在同一个SqlSession中,依然只访问一次数据库,并且我们可以从日志中清楚的看到第二次查询是从缓存中获取的:
但是如果两次查询之间做了增删改操作,那么同样会使得缓存中的数据被清空:
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();
IUserInfoDao userInfoDao = session.getMapper(IUserInfoDao.class);
List<UserInfo> list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
session = factory.openSession();
userInfoDao = session.getMapper(IUserInfoDao.class);
userInfoDao.delete("'%四%'");
list = userInfoDao.selectAll();
System.out.println(list.size());
session.close();
运行结果如下:
从中我们看到了Mybatis缓存技术的好处,同时也发现了其中存在的问题,比如,我们仅仅修改数据库中的一条数据,它就会把缓存给清空了,其他查询就只能再次访问数据库了。