mybatis中的一级缓存和二级缓存

一、mybatis缓存基本介绍

1、缓存:将相同查询条件的sql语句执行一遍后得到的结果存在内存或者某种缓存介质中,当下次遇到一模一样的查询sql时候不在执行sql与数据库交互,而是直接从缓存中获取结果,减少服务器的压力;
2、mybatis的查询缓存又分为一级缓存和二级缓存,一级缓存的作用范围为同一个sqlsession,而二级缓存的作用范围为同一个namespace和mapper。

二、一级缓存

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。

一级缓存的工作原理:
在这里插入图片描述
用户发起查询请求,查找某条数据,sqlSession先去缓存中查找,是否有该数据,如果有,读取;
如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。
但如果sqlSession执行commit(即增删改操作),就会清空缓存,这么做的目的是为了避免脏读。

上面说过一级缓存的作用域是同一个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代码片段连续调用两次,控制台日志打印如下:
在这里插入图片描述
在这里插入图片描述
未正确使用一级缓存

由两张图可以看出上面的代码执行了两次与数据库的会话,因为每次都新建了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;
}

执行结果如下图:
在这里插入图片描述
正确使用一级缓存结果

可以看到开启spring事务之后,同样的业务代码,在这里sql片段只打印一遍,而且在执行第二次查询的时候没有在重新创建sqlsession,所以是同一个sqlsession,说明第二次查询的数据是从缓存中获取的,所以证明这里是一级缓存起了作用;而关于spring事务管理的service只会一个创建sqlsession,这是因为事务管理下的sql执行方式是BATCH,只会与数据库交互一次,一次执行完所有的sql,所以只会创建一个sqlsession。

三、二级缓存

既然有了一级缓存,那么为什么要提供二级缓存呢?

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的作用范围更大。

还有一个原因,实际开发中,MyBatis通常和Spring进行整合开发。Spring将事务放到Service中管理,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。

二级缓存原理:
在这里插入图片描述
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

开启二级缓存:
1,打开总开关
在mybatis-config.xml文件中的标签配置开启缓存:

<settings>  
   <!--开启二级缓存-->  
    <setting name="cacheEnabled" value="true"/>  
</settings> 

2,在需要开启二级缓存的mapper.xml中加入caceh标签

<cache/>

3,让使用二级缓存的POJO类实现Serializable接口

public class User implements Serializable {}

测试:

@Test
public void testCache2() throws Exception {
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
    User user1 = userMapper1.findUserById(1);
    System.out.println(user1);
    sqlSession1.close();
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    User user2 = userMapper2.findUserById(1);
    System.out.println(user2);
    sqlSession2.close();
}

输出结果:

DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 103887628.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=张三, sex=1, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - Returned connection 103887628 to pool.
DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.5
User [id=1, username=张三, sex=1, birthday=null, address=null]

我们可以从打印的信息看出,两个sqlSession,去查询同一条数据,只发起一次select查询语句,第二次直接从Cache中读取。

前面我们说到,Spring和MyBatis整合时, 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。那么如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。

总结:
对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。

但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
举个简单的例子:
订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。

根据以上,想要使用二级缓存时需要想好两个问题:
1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。

参考文章:https://blog.csdn.net/weixin_36380516/article/details/73194758
参考文章:https://blog.csdn.net/LLF_1241352445/article/details/81146054

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值