这篇文章我们将主要讲一下Mybatis的缓存。
目录
3.5.3 sqlSession相同,两次查询之间执行了增删改操作!
3.7.1 在核心配置文件中,设置全局属性caheEnable="true"。
3.7.2 在UserDao.xml配置文件声明使用二级缓存
3.7.4 二级缓存必须在SqlSession关闭或提交之后有效
1.为什么要有Mybatis缓存
在前面的文章中说过,Mybatis是基于jdbc而来的,是对jdbc的一种优化。因为jdbc存在一些问题,所以我们需要使用Mybatis。那么jdbc存在哪些问题?
jdbc存在的问题:
- 数据库连接的频繁创建、释放浪费资源进而影响系统性能。
- sql代码写在 java文件当中,如果在开发过程中我们改动某个sql,就需要去修改java代码,改完之后还需要重新编译。
- 对结果集的解析也是硬编码,sql变化会导致解析结果的代码也跟着变化,系统不易维护
对于第二个问题,Mybatis是通过xml文件也就是配置文件的方式来解决的。之前,我们的sql是写在servlet类中的,是一个.java文件,每次修改后需要重新编译、解析,而Mybatis的sql语句是写在xml文件也就是配置文件中的,修改后不需要重新编译
对于第三个问题,Mybatis是通过Javabean对象来解决的。Mybatis是通过Javabean对象来完成对结果集的解析的,是Javabean对象的一种交互,就不是硬解析了。
对于第一个问题,Mybatis是通过缓存来解决的,这就是这篇文章主要要讲的内容。
2,什么是缓存
2.1 什么是缓存
- 缓存:就是存储在内存当中的数据
- 将用户常使用的数据存放在缓存(内存)当中,用户查询数据就不用从磁盘(关系型数据库文件)当中查询,从缓存当中查询,从而提高查询效率,解决了高并发系统的性能问题
我们可以看一下下面这张图片:
2.2 为什么使用缓存
为了减少和数据库的交互次数,减少系统开销,提高系统效率
2.3 什么样的数据能使用缓存
经常查询并且不经常改变的数据
多说一句题外话:数据库的数据是存储在硬盘中的
3.Mybatis缓存
3.1 概述
mybatis包含了一个非常强大的查询缓存特性,他可以非常方便的定制和配置缓存。缓存可以极大的提高查询的效率
mybatis系统当中默认定义了两级缓存:一级缓存和二级缓存
默认情况之下,只有一级缓存开启(sqlSession级别的缓存),二级缓存需要手动开启配置,需要局域namespace级别的缓存。
3.2 SQLSession工厂的说明
现在,对上述图片做一个解析。
第25行,@Before是一个注解,是前置通知,表示下面内容是在Test之前运行的,@After和这个一个道理。
第28行,加载主配置文件,即sqlMapConfig.xml文件,这个文件里有driver,有url,有用户名,有密码,以及我们所关联的xml配置文件。加载成什么?加载成字节流文件,看第21行的定义。
第30行,将第28行所加载的字节流文件交给SqlSessionFactory工厂进行解析,仔细观察这个类的创建。SqlSessionFactory是怎么解析的?是通过硬匹配进行解析的,这个与SqlSessionFactory源码有关,这里不过多赘述,有兴趣的可以点进去看源码。
第32行,我们将最终解析出来的内容生成一个session对象
第34行,我们通过session对象来调用相关的接口来生成代理对象mapper。
其中,和数据库进行交互的还是我们的SqlSession对象,SqlSession中是有许多方法的。
题外话:这一块特别有意思,我前面也写过对这部分的一些理解,现在理解加深了,就又写了点,这块要多想,同时还要结合前面的那张图。
3.3 一级缓存
一级缓存也叫本地缓存。
- 与数据库同一次会话期间查询到的数据会放入的本地缓存当中。
- 如果以后需要获取相同的数据直接去缓存当中拿,没必要再去查询数据库
3.4 测试一级缓存
我们书写以下代码(当然,相关的内容你得有):
@Test
public void find() throws IOException {
//加载主配置文件,目的是为了构建SqlSessionFactory对象
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//通过SqlSessionFactory工厂对象创建SqlSesssion对象
session = factory.openSession();
//通过Session创建UserDao接口代理对象
mapper = session.getMapper(UserDao.class);
User user1 = mapper.findById(1);
System.out.println(user1.toString());
System.out.println("-----------------");
User user2 = mapper.findById(1);
System.out.println(user2.toString());
System.out.println(user1 == user2);
//释放资源
session.close();
in.close();
}
然后,截图如下:
重点关注第60行
然后,我们运行,看下结果:
解释true的原因:
当我们第一次执行sql语句时,SqlSession会从数据库中拿出数据,放入local catch中(就是本地缓存),然后当我们第二次执行此sql语句时,SqlSession直接从local catch中拿数据,所以,两次执行sql的地址是一样的,所以为true,这就是Mybatis的一级缓存。
3.5 一级缓存失效的情况
以下四种情况,一级缓存会失效:
- sqlSession不同
- sqlSession相同,查询条件不同
- sqlSession相同,两次查询之间执行了增删改操作!
- sqlSession相同,手动清除一级缓存
3.5.1 SqlSession不同
很好理解,SQLSession都不是同一个了嘛,最后的地址怎么可能是同一个
3.5.2 sqlSession相同,查询条件不同
sqlSession相同,查询条件不同,会导致一级缓存失效,但是,多次查询不同的情况,不会导致缓存失效
很简单,因为你拿的数据都不一样,执行的sql都不一样,怎么可能相同嘛
3.5.3 sqlSession相同,两次查询之间执行了增删改操作!
因为你数据库改变了,缓存刷新了(我自己瞎猜的),所以就是false咯
3.5.4 sqlSession相同,手动清除一级缓存
废话,你清除了,它当然就没有了,很简单的啦。
3.6 二级缓存
Mybatis的二级缓存非常强大,它不同于一级缓存只存在于sqlSession的生命周期中,而是可以理解为存在于SQLSessionFactory的生命周期中。
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactroy创建SqlSession查询结果会被缓存;此后若再次执行相同的查询语句,结果会从一个缓存中获取。
3.7 测试二级缓存
二级缓存的开启有以下四个条件:
①:在核心配置文件中,设置全局属性caheEnable="true"。
②:在映射件中置
③:查询数据所转换的实体类类型必须实现序列化接口
④:二级缓存必须在SqlSession关闭或提交之后有效
下面,我们依次执行这四个条件。
3.7.1 在核心配置文件中,设置全局属性caheEnable="true"。
在SqlMapConfig.xml配置文件中开启二级缓存
<!‐‐ 开启二级缓存 ‐‐>
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
3.7.2 在UserDao.xml配置文件声明使用二级缓存
<!--使用二级缓存-->
<cache/>
3.7.3 查询数据所转换的实体类类型必须实现序列化接口
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// get set方法 .....
}
3.7.4 二级缓存必须在SqlSession关闭或提交之后有效
@Test
public void findById() throws IOException {
// 1.加载SqlMapConfig配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建sqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.sqlSessionFactory创建sqlSession
SqlSession sqlSession = factory.openSession();
SqlSession sqlSession2 = factory.openSession();
//4.通过Session创建UserDao接口代理对象
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user1 = mapper.findById(1);
System.out.println(user1.toString());
// 将其一级缓存的数据放进二级缓存中,并清空一级缓存
sqlSession.close();
System.out.println("-----------------");
UserDao mapper2 = sqlSession2.getMapper(UserDao.class);
User user2 = mapper2.findById(1);
System.out.println(user2.toString());
// 将其一级缓存的数据放进二级缓存中,并清空一级缓存
sqlSession2.close();
System.out.println(user1 == user2);
resourceAsStream.close();
}
3.7.5 测试结果
重要结论:打印发现2个对象的地址值不一样,但是确实只发送了一次SQL语句的查询,二级缓存中存储的是数据,不是对象。
3.8 Catch参数的具体细节
eviction(收回策略)
(session1在catch中注入一条缓存,session2在catch中注入一条缓存,大家都注入,满了怎么办?所以要回收,从catch中回收缓存的策略就叫(缓存)收回策略)
- LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。
- FIFO(先进先出):按对象进入缓存的顺序来移除它们。
- SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。
- WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushinterval(刷新间隔) 可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。
(数据库中的数据发生变化后,缓存如果不发生变化,那会出问题的,所以缓存也需要发生变化,要保持和数据库中数据的一致性,怎么办?刷新扫描呗)
- 默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。
size(引用数目)
- 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。
readOnly(只读) 属性可以被设置为 true / false。
- true:只读缓存:会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改, 这提供了很重要的性能优势。
- false读写缓存: 通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。
配置结果:
<cache eviction="FIFO"
flushInterval="6000"
size="512"
readOnly="true"/>
4. Mybatis缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序查询出来的数据,可以直接拿来使用
- 如果二级缓存命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存的数据会写入二级缓存