一:本文将涉及到的如下几方面的知识点
mybatis基本增删改查
Java序列化和反序列化
JDBC和sqlsession基本理论
spring事务管理
二:mybatis查询缓存基本介绍
缓存:将相同查询条件的sql语句执行一遍后所得到的结果存在内存或者某种缓存介质当中,当下次遇到一模一样的查询sql时候不在执行sql与数据库交互,而是直接从缓存中获取结果,减少服务器的压力;
mybatis的查询缓存又分为一级缓存和二级缓存,一级缓存的作用范围为同一个sqlsession,而二级缓存的作用范围为同一个namespace和mapper;这是接下来的篇幅重点介绍的;
三:mybatis缓存详细介绍
1:一级缓存
一级缓存是mybatis默认就帮我们开启的,我们不需要多做配置,但是我们得知道其中原理,否则我们也不知道怎么使用,也不知道我们到底有没有一级缓存。
上面第二部分说过一级缓存的作用域是同一个sqlsession,sqlsession的作用就是建立和数据库的会话,我们对数据库表的增删改查都是通过sqlsession去执行指定的sql完成的,而sqlsession和数据库的连接并不是永久连接的,也一定要杜绝这种永久连接;所以就有了sqlsession的创建和关闭,sqlsession默认执行完一段的sql片段后就会close掉sqlsession,即销毁sqlsession;而下一次对数据库的操作的又会重新建立会话关系,即建立新的sqlsession,所以这就和前一次的执行sql的sqlsession属于不同的SQL session了,也就这两个sqlsession就不存在一级缓存关系了;代码分析如下:
public CourseDto getCourse(String courseId) {
log.info("测试mybatis默认开启的一级缓存");
log.info("第一次查询");
Course course = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
log.info("第二次查询");
Course course1 = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
CourseDto courseDto = mapper.map(course, CourseDto.class);
return courseDto;
}
我们说过一级缓存不需要任何配置,mybatis默认开启的,所以这里直接写service,没有其他额外配置;代码内容是对同一个sql代码片段连续调用两次,控制台日志打印如下:
图1-1:未正确使用一级缓存
图1-2:未正确使用一级缓存
由两张图可以看出上面的代码执行了两次与数据库的会话,因为每次都新建了sqlsession,不在一级缓存的作用域内,而且每次执行的sql也都帮我们打印出来了,所以说程序并没有用到我们的一级缓存;那么如何将一级缓存利用起来呢?这就需要用到我们的spring事务管理;现在的代码如下:
@Transactional(propagation = Propagation.REQUIRES_NEW)//开启spring的申明事务
@Override
public CourseDto getCourse(String courseId) {
log.info("测试mybatis默认开启的一级缓存");
log.info("第一次查询");
Course course = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
log.info("第二次查询");
Course course1 = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
log.info("第二次执行查询完毕");
CourseDto courseDto = mapper.map(course, CourseDto.class);
return courseDto;
}
执行结果如下图:
图2:正确使用一级缓存结果
可以看到开启spring事务之后,同样的业务代码,在这里sql片段只打印一遍,而且在执行第二次查询的时候没有在重新创建sqlsession,所以是同一个sqlsession,说明第二次查询的数据是从缓存中获取的,所以证明这里是一级缓存起了作用;关于spring事务不知如何使用的,可以参看我的另一篇文章(https://blog.csdn.net/llf_1241352445/article/details/79831031);而关于spring事务管理的service只会一个创建sqlsession,这是因为事务管理下的sql执行方式是BATCH,只会与数据库交互一次,一次执行完所有的sql,所以只会创建一个sqlsession;
2:二级缓存
二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,所以这里,我们需要对mybatis的配置修改,开启二级缓存设施,而且需要在我们的namespace下开启缓存,具体如下
在mybatis-config.xml文件中的<settings>标签配置开启缓存,代码如下:
<!--开启缓存,此时配置的是mybatis的二级缓存-->
<setting name="cacheEnabled" value="true"/>
单单配置这个还是不够的,还需要在我们的mapper的xml文件下开启缓存,即加入该标签:
<!--namespace必须加上此标签才会开启二级缓存-->
<cache />
然后可以开始试着运行我们的代码如下:
@Override
public CourseDto getCourse(String courseId) {
log.info("测试mybatis默认开启的二级缓存");
Course course = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
log.info("第一次查询结果:{}",course.toString());
Course course1 = courseDao.getSqlSession().selectOne("listCourseByAutoMapping", courseId);
log.info("第二次执行查询完毕:{}",course1.toString());
CourseDto courseDto = mapper.map(course, CourseDto.class);
return courseDto;
}
执行结果如下:
图三:序列化异常
执行结果报序列化异常,那是因为我们映射的pojo对象未实现序列化接口,说明我们从缓存数据中读取数据需要进行反序列化,这是因 为mybatis的二级缓存的缓存介质有多种多样,而并不一定是在内存中,所以需要我们对pojo对象进行序列化,只要实现序列化接口即可,而对于序列化和反序列化不清楚的读者可以参看我的另外一篇文章(https://blog.csdn.net/llf_1241352445/article/details/81135882),pojo序列化的就不截图了,直接看我们的正确执行结果,如下图:
图四:二级缓存效果
可以看到,第一次查询有打印sql,而第二次查询没有打印sql,而且还有正确的返回数据,而上面的代码也没有开启spring事务
,说明不存在mybatis的一级缓存,唯一能起作用的就是二级缓存了;
3:拓展延申
对于同一个service被同一个请求分多次调用,则所有的这些调用之间的一级缓存和二级缓存个事什么情况呢?
一级缓存是基于sqlsession的,当一次请求结束之后,意味着session执行结束,所以sqlsession也就关闭了,所以缓存也就清空了,因此,对于多次的请求,肯定不存在一级缓存;
二级缓存是对于namespace和mapper的,只要相同的查询sql,都会优先从缓存区域查找,所以对于多次请求,对于二级缓存是没有影响的。这个大家可以去自己验证下,只要连续发两次的请求验证sql的打印次数就可以得到结论;