什么是缓存(Cache)
- 缓存(Cache)就是:存储在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,当用户再次查询相同数据时就不用从磁盘上(关系型数据库数据文件)查询了,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
MyBatis中的缓存机制
缓存的作用:通过减少IO的方式,来提高程序的执行效率。
缓存就是指存在内存中的临时数据,使用缓存能够减少和数据库交互的次数,提高效率。将相同查询条件的sql语句执行一遍后得到的结果存在内存或者某种缓存介质中,当下次遇到一模一样的查询sql时候不在执行sql与数据库交互,而是直接从缓存中获取结果,减少服务器的压力;
MyBatis缓存包括什么
MyBatis的缓存包括一级缓存和二级婚车。一级缓存是会话级别的缓存,二级缓存是全局级别的缓存。一级缓存是默认开启的,基于会话的,通过同一个会话的多次查询来提高性能。而二级缓存是可以手动操作的,可以多个会话共享数据,提供了更广法的缓存范围,其原理是利用了对象引用和HashMap来实现缓存数据的存储和快速检索。
注意:缓存只针对DQL语句,也就是缓存机制只对应Select语句。
一级缓存
-
一级缓存又称为“本地缓存”。
-
一级缓存的生命周期与一个SqlSession对象的生命周期相同,当一个SqlSession对象关闭时,与之对应的一级缓存同步清除。
-
与数据库同一次会话(SqlSession)期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中获取,不会再去查询数据库。
测试:
@Test
public void test1() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
//只执行了一条sql语句
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
Student student1 = mapper1.queryStudentById(2);
System.out.println(student1);
Student student2 = mapper1.queryStudentById(2);
System.out.println(student2);
sqlSession1.close();
}
执行结果:
执行结果只有一条select语句,但是存在两次查询结果,也就是第二次时从缓存里边得到。
一级缓存失效的情况:
- 一级缓存是默认开启的,我们没办法关闭它。
- 一级缓存失效情况:没有使用当前的一级缓存,还需要再向数据库中发起一次查询请求。
不同的sqlSession:
package com.atangbiji.dao;
import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MapperTest {
@Test
public void TestQueryBlogById2(){
Blog blog1 = new Blog();
Blog blog2 = new Blog();
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
}
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第二次查询结果为:" + blog2);
}
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}
运行测试程序TestQueryBlogById2
,执行结果如下图所示:
观察结果:发现发送了两条SQL
语句!
**(2)sqlSession
相同,查询条件不同 **
package com.atangbiji.dao;
import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MapperTest {
@Test
public void TestQueryBlogById3(){
//1、从SqlSessionFactory中获取SqlSession对象
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
//2、执行SQL语句
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
System.out.println("第一次查询结果为:" + blog1);
Blog blog2 = mapper.queryBlogById("7aa61e8d1e2f40db89d47b1b18ba629a");
System.out.println("第二次查询结果为:" + blog2);
//3、判断是否为同一个对象
System.out.println(blog1 == blog2);
}
}
}
运行测试程序TestQueryBlogById3
,执行结果如下图所示:
观察结果:发现发送了两条SQL
语句!
结论:当前缓存中,不存在这个数据
MyBatis二级缓存
- 二级缓存也叫“全局缓存”,一级缓存作用域太低,所有产生了二级缓存。
- 二级缓存是基于命名空间(namespace)级别的缓存。
- 一个命名空间(XML映射配置文件),对应一个二级缓存。
注:二级缓存只作用于cache 标签所在的XML映射文件中的语句。
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中,如果当前会话关闭了,该对应的一级缓存就关闭了,此时,一级缓存中的数据就被转到了二级缓存中,新的会话查询信息,就会在二级缓存中获取内容,不同的
mapper 查出的数据会放在自己对应的缓存中
使用二级缓存需要具备以下几个条件:
- 这个配置表示启用 MyBatis 的二级缓存功能。默认就是true,⽆需在配置文件设置。
- 在需要使⽤⼆级缓存的StudentMapper.xml⽂件中添加配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:Mapper接口的全路径,使其和Mapper对应-->
<mapper namespace="com.yjg.mapper.StudentMapper">
<!-- 默认情况下,二级缓存机制是开启的。-->
<!-- 只需要在对应的StudentMapper.xml文件中添加以下标签。用来表示”我"使用该二级缓存。-->
<cache/>
<delete id="delete">
DELETE from student where stu_id=${id}
</delete>
<select id="queryStudentById" parameterType="integer" resultType="Student">
select * from student where stu_id=${id}
</select>
</mapper>
注:
- eviction:清除策略,默认为LRU。可用的清除策略有:
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
- flushInterval:刷新间隔,单位(ms)
- size:存储对象或列表的引用数目(想缓存对象的大小和运行环境中可用的内存资源,默认值是 1024。)
- readOnly:只读。
- 只读的缓存会给所有调用者返回缓存对象的相同实例,这些对象不能被修改,性能提升明显。
- 而可读写的缓存会(通过序列化)返回缓存对象的拷贝,速度上会慢一些,但是更安全。
- 默认值为false
- 使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝
@Data
public class Student implements Serializable {
private Integer stuId;
private String stuName;
private Integer stuAge;
private Double stuSalary;
private Date stuBirth;
private Date createTime;
private Integer courseId;
}
- SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤
@Test
public void test5() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。>
Student student1 = mapper1.queryStudentById(2);
System.out.println(student1);
//如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。
//这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)
Student student2 = mapper2.queryStudentById(2);
System.out.println(student2);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
sqlSession1.close();
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
sqlSession2.close();
}
测试:
@Test
public void test5() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。>
Student student1 = mapper1.queryStudentById(2);
System.out.println(student1);
//如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。
//这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)
Student student2 = mapper2.queryStudentById(2);
System.out.println(student2);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
sqlSession1.close();
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
sqlSession2.close();
}
可以看见执行结果,存在一个Cache Hit Ratio [com.yjg.mapper.StudentMapper]: 0.0,就是因为在StudentMapper.xml文件中加了
可以看到,缓存命中率(Cache Hit Ratio)是0,执行了两个select语句,说明二级缓存没有生效
如果将关闭位置换一下,就可以让二级缓存生效
@Test
public void test5() throws Exception {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。>
Student student1 = mapper1.queryStudentById(2);
System.out.println(student1);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
sqlSession1.close();
//如果一级缓存中没有找到对应的缓存结果,MyBatis 会尝试从二级缓存中查找,发现存在就会使用二级缓存
Student student2 = mapper2.queryStudentById(2);
System.out.println(student2);
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
sqlSession2.close();
}
总结这段代码
这段代码演示了一级缓存和二级缓存的使用。首次查询时会先从数据库查询
并将结果放入一级缓存,在关闭 SqlSession
前会将一级缓存数据写入二级缓存。再次查询时,如果一级缓存没有找到结果,则会尝试从二级缓存中获取结果,避免了再次访问数据库
结论:
- 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据。
- 查出的数据都会被默认先放在一级缓存中。
- 只有会话提交或者关闭时,一级缓存中的数据才会转到二级缓存中。