缓存及缓存机制 【解读Mybatis缓存机制】?传统的关系型数据库,十分强调数据的一致性,并为此降低读写性能付出了巨大的代价,虽然关系型数据库存储数据和处理数据的可靠性很不错,但一旦面对海量数据的处理的时候效率就会变得很差,特别是遇到高并发读写的时候性能就会下降的非常厉害。
比如这样一个场景:使用sql语句(select * from user where id =1;
)查询了数据库中id为1的数据,此时数据库会进行查询操作,并返回一个User类型的数据,没有进行增删改的操作,然后再次使用sql语句查询数据库,程序会重复上面的步骤.在这种频繁的查询操作下,对数据库来说是一个巨大的挑战.
?所以我们引入了缓存(cache)
,将经常不被修改并且频繁查询的数据放入内存中,减少对数据库的频繁读写操作,降低数据库的压力.当要读取数据时,会首先从内存中查找需要的数据,如果找到了则直接执行,找不到的话则从数据库中找。由于内存的运行速度比从数据库中查询快得多,故缓存的作用就是帮助查询更快地运行。
?Mysql缓存机制
就是缓存sql 文本及缓存结果,用KV形式(是一种以键值对存储数据的一种形式,可以理解为一个大的map,每个键都会对应一个唯一的值。)保存再服务器内存中,如果运行相同的sql,服务器直接从缓存中去获取结果,不需要在再去解析、优化、执行sql。 如果这个表修改了,那么使用这个表中的所有缓存将不再有效,查询缓存值得相关条目将被清空。
?对于频繁更新的表,查询缓存不合适,对于一些不变的数据且有大量相同sql查询的表,查询缓存会节省很大的性能。
Mybatis缓存机制 ?和大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持;
?一级缓存是Mybatis默认开启的,是基于 PerpetualCache 的 HashMap 本地缓存,存储作用域为 Session,当 Session flush (会话刷新)或 close 之后,该Session中的所有 Cache 就将清空。
?二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。
一级缓存 ?在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。
文章图片
?每个SqlSession中持有了Executor(执行器),每个Executor中有一个LocalCache(本地缓存)。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement(在MyBatis启动时,会解析这些包含SQL的XML文件,并将其包装成为MapperStatement对象,并将MapperStatement注册到全局的configuration对象上),在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。具体实现类的类关系图如下图所示。
文章图片
代码流程
- 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
- 第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
注意事项:
- 如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
- 当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
一级缓存的开启与关闭
- 一级缓存默认开启
- 关闭一级缓存需要在mybatis配置文件的settings标签设置
="localCacheScope" value="https://www.it610.com/article/SESSION"/>
导致一级缓存不命中的原因
- 一级缓存关闭;
- 一级缓存开启,但是使用了不同的SqlSession进行查询;
- 使用相同的SqlSession,但是查询条件发生了变化;
- 使用了相同的查询条件,但是两次查询之间SqlSession执行了commit、clearCache或close(关闭之后查询会报错)操作;
- 使用了相同的查询条件,但是两次查询之间SqlSession执行了insert、update或delete操作,此时无论三种标签的
flushCache 属性是否为 false,都会清空 sqlSession的缓存; - select标签的flushCache属性为true
清空一级缓存的操作
- SqlSession调用commit方法;
- SqlSession调用clearCache方法;
- select标签的flushCache属性为true;
SqlSession执行了insert、update或delete操作,此时无论三种标签的flushCache属性是否为 false;
一级缓存实验
实验1
?开启一级缓存,范围为会话级别,调用三次Getbyid
代码如下所示:
public static void main(String[] args) throws IOException {
}
SqlSession ss;
Userdao udao;
//定义一个解析工具
@Before
public voidsetup() throws IOException {String resource = "MybatisConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
ss = sqlSessionFactory.openSession();
udao=ss.getMapper(Userdao.class);
}
@Test
public void Getbyid() {
System.out.println(udao.Getbyid1(1));
System.out.println(udao.Getbyid1(1));
System.out.println(udao.Getbyid1(1));
System.out.println(udao.Getbyid1(1));
}
@After
public void finish() {
ss.close();
}
执行结果:
文章图片
实验2
?增加了对数据库的修改操作,验证在一次数据库会话中,如果对数据库发生了修改操作,一级缓存是否会失效。
代码如下
public static void main(String[] args) throws IOException {
}
SqlSession ss;
Userdao udao;
//定义一个解析工具
@Before
public voidsetup() throws IOException {String resource = "MybatisConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
ss = sqlSessionFactory.openSession();
udao=ss.getMapper(Userdao.class);
}
@Test
public void Getbyid() {
System.out.println(udao.Getbyid1(1));
System.out.println(udao.Getbyid1(1));
User user = new User();
user.setId(1);
user.setSex("男");
System.out.println("更新数据"+udao.UpdateUser(user));
System.out.println(udao.Getbyid1(1));
System.out.println(udao.Getbyid1(1));
}
@After
public void finish() {
ss.close();
}
执行结果:
文章图片
实验3
?开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库,验证一级缓存只在数据库会话内部共享。
执行代码:
public static void main(String[] args) throws IOException {
}
SqlSession ss;
Userdao udao;
SqlSession ss2;
Userdao udao2;
//定义一个解析工具
@Before
public voidsetup() throws IOException {String resource = "MybatisConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
ss = sqlSessionFactory.openSession();
udao=ss.getMapper(Userdao.class);
ss2 = sqlSessionFactory.openSession();
udao2=ss2.getMapper(Userdao.class);
}
@Test
public void Getbyid() {
System.out.println(udao.Getbyid(1));
System.out.println(udao.Getbyid(1));
System.out.println(udao2.Getbyid(1));
System.out.println(udao2.Getbyid(1));
}
@After
public void finish() {
ss.close();
}
执行结果:
文章图片
一级缓存小结
- MyBatis一级缓存的生命周期和SqlSession一致。
- MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺
- MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
文章图片
?二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
?当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
二级缓存的配置 ?要正确的使用二级缓存,需完成如下配置的。在MyBatis的全局配置文件中开启二级缓存。
>
="cacheEnabled" value="https://www.it610.com/article/true"/>默认是false:关闭二级缓存
>
在MyBatis的映射配置文件XML中配置cache或者 cache-ref 。
cache标签用于声明这个namespace使用二级缓存,并且书写属性可以自定义配置。
- type:cache使用的类型,默认是PerpetualCache(Mybatis自带的二级缓存),也可以使用第三方的插件
- eviction: 定义回收的策略,常见的有FIFO,LRU。
- flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
- size: 最多缓存对象的个数。
- readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
- blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
当前mapper下所有语句开启二级缓存
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
po 类(实体类)实现 Serializable 序列化接口
public class User implements Serializable{....}
二级缓存实验
实验1
测试二级缓存效果,不提交事务,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。
执行代码:
@Test
public void testCacheWithoutCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
}
执行结果:
文章图片
实验2
测试二级缓存效果,当提交事务时,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。
执行代码:
@Test
public void testCacheWithCommitOrClose() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
sqlSession1.commit();
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
}
执行结果:
文章图片
实验3
测试update操作是否会刷新该namespace下的二级缓存。
执行代码:
@Test
public void testCacheWithUpdate() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
SqlSession sqlSession3 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
sqlSession1.commit();
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
studentMapper3.updateStudentName("方方",1);
sqlSession3.commit();
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
}
执行结果:
文章图片
本人个人网站: https://www.icnfox.cn 欢迎来访
有任何问题可以在个人网站的评论区留言,看到就会第一时间回复 啾咪ヾ(≧▽≦*)o
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)