Marco's Java【Mybatis进阶(四) Mybatis缓存及源码跟进】

前言

终于到了我们Mybatis的完结篇啦,接下来我们要讲的内容大家应该都很熟悉,它就是缓存
缓存出现在我们编程世界的各个角落,扮演的角色也相当重要,比如说我们数据库的缓存,又好比说我们浏览器的缓存等等,缓存不仅仅加快了我们查询的速度,也变相的降低了我们服务器的压力,本节我们就针对Mybatis自带的缓存机制来研究一下。

Mybatis一级缓存

我们知道,Mybatis在查询数据库的数据的时候,其实是件耗时的工作,当然也考验我们系统的性能,那么这个时候我们通过使用缓存,能够适当的减少数据库和系统的压力
Mybatis一级缓存默认是处于开启的状态,它又被称为sqlSession缓存,怎么理解呢?
大家学习过Servelt应该知道作用域对象吧,作用域大小从ServeltContext、HttpSession、HttpServletRequest、PageContext依次减小,那么sqlSession又被称之为Sql会话,当然也是有作用域之分的,只不过没有像Servlet这样划分的这么细,所以这么说大家应该明白了,sqlSession缓存也就是在sqlSession生命周期前生效,一般来说sqlSession被commit或者close了,当前的Sql会话也就结束了

那接下来我们通过测试来验证一下我们的想法

public class TestMybatis {
	public static void main(String[] args) {
		SqlSession session = MybatisUtil.getSession();
		UserMapper mapper = session.getMapper(UserMapper.class);
		List<User> users = mapper.fuzzyQuery("mar");
		for (User user : users) {
			System.out.println(user);
		}
		List<User> users1 = mapper.fuzzyQuery("mar");
		for (User user : users1) {
			System.out.println(user);
		}
		session.commit();
		session.close();
	}
}

在这里插入图片描述
通过运行的结果,不难看出Mybatis打开了一次Connection,并且Preparing(也就是PrepareStament)只准备了一次Sql,可我们的查询语句List<User> users1 = mapper.fuzzyQuery("mar");写了两遍,结果也运行了两遍,按照常理来说,Sql应该准备两次并且运行两遍对吧?这是否就印证了Mybatis默认开启了缓存呢?
如果这样还不能够证明我们再来看看Mybatis的源码。
我们还是以openSession()为入口来查看源码,看缓存是否默认开启主要关注的是Mybatis的配置
在这里插入图片描述
那么可以看到这里有一个configuration.getDefaultExecutorType()方法,我们点击openSessionFromDataSource()查看他的实现方法发现里面有一个Executor,它是我们Mysql的sql执行器
在这里插入图片描述

我们再次点击查看Executor接口的实现类,发现他的实现类有很多,我们来找一个跟缓存有关的
在这里插入图片描述
好像有一个叫CachingExecutor跟缓存有关系,好!决定就是你了!
但是发现里面好像没有和缓存默认开启相关的方法,我们再回过头来看configuration的类Configuration
在这里插入图片描述
终于找到啦,cacheEnable默认为true开启了缓存,当cacheEnable为true,executor会被包一层装饰器,就是我们刚刚看到的CachingExecutor,cacheEnabled其实正确来说是我们Mybatis XML的全局配置文件中settings节点中cacheEnabled子节点的值,因此,就算我们不配置cacheEnabled=true,它默认也是true。

那么CachingExecutor到底有什么用呢?这就涉及到我们二级缓存的知识点了~

Mybatis二级缓存

Mybatis二级缓存,又名:sqlSessionFactory缓存,也就是作用域范围在同一个sqlSessionFactory作用范围内的缓存,那继续聊到我们上面的话题,打开CachingExecutor会发现有一个方法query(),既然CachingExecutor也是Executor的实现类,而Executor是Sql执行器,那么CachingExecutor当然也应该是Sql执行器啦,只不过他是带缓存功能的Sql执行器。
在这里插入图片描述
我们来看上面的这个方法叫ms.getCache,这里的Cache指的就是我们的二级缓存了,那么当cache为null的时候,我们会直接执行delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);这段代码的意思就是,如果没有缓存,则直接去数据库查找数据,当cache不为null的时候我们会到缓存中去找数据,源码查看到delegate.getObject(key);就无法再继续查看了,但是我们应该清楚,我们缓存就是一个Map集合,key就是我们Sql,value就是之前查询到的结果。查询到之后会返回一个List,那么当List为null的时候我们依然会再次去数据库中进行查询。

public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    if (object == null) {
     entriesMissedInCache.add(key);
   	}
    if (clearOnCommit) {
     return null;
    }
    return object;
 }

讲了这么多了,给大家再演示一个栗子,将之前的代码做了一个小小的改动

public class TestMybatis {
	public static void main(String[] args) {
		SqlSession session = MybatisUtil.getSession();
		UserMapper mapper = session.getMapper(UserMapper.class);
		List<User> users = mapper.fuzzyQuery("mar");
		for (User user : users) {
			System.out.println(user);
		}
		session.commit();//第一次commit
		List<User> users1 = mapper.fuzzyQuery("mar");
		for (User user : users1) {
			System.out.println(user);
		}
		session.commit();//第二次commit
		session.close();
	}
}

是不是发现Preparing准备了两次Sql,这是否又印证了我们之前提到的Mybatis一级缓存的作用域在sqlSession中
的想法
在这里插入图片描述
那怎么开启二级缓存呢?非常简单,首先在properties标签下新建一个settings,settings的作用一般都是让一些配置生效,例如我们之前的log4j(配置可以缺省,系统默认会在资源路径下查找log4j.properties),还有我们的缓存配置,这个设置默认也是可以缺省的,value为false,则缓存不生效。
在这里插入图片描述
接下来在resultMap标签上方添加一个<cache>标签即可
在这里插入图片描述
配置完成后我们来测试一下我们之前使用过的代码,大家还记得我们开启一级缓存的时候以下代码的运行结果吗?
当开启一级缓存时,以下代码的Preparing语句是会执行两次的,那么我们来看看这次运行的结果如何

public class TestMybatis {
	public static void main(String[] args) {
		SqlSession session = MybatisUtil.getSession();
		UserMapper mapper = session.getMapper(UserMapper.class);
		List<User> users = mapper.fuzzyQuery("mar");
		for (User user : users) {
			System.out.println(user);
		}
		session.commit();
		List<User> users1 = mapper.fuzzyQuery("mar");
		for (User user : users1) {
			System.out.println(user);
		}
		session.commit();
		session.close();
	}
}

可以很清楚的发现,我们的Preparing语句只执行了一次!
这段DEBUG [main] - Cache Hit Ratio [com.marco.dao.UserMapper]: 0.5执行就代表,我们此次查询到的结果,是从缓存中取出来的。
在这里插入图片描述

Cache使用时的注意事项

  • 只能在【只有单表操作】的表上使用缓存,不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace下。

  • 在可以保证查询远远大于insert,update,delete操作的情况下使用缓存,否则就没有必要使用缓存了。

  • insert,update,delete操作会清空所在namespace下的全部缓存


后语

既然二级缓存的缓存范围更广,那为什么不直接设置使用二级缓存呢?甚至说我们还要避免使用二级缓存?
在上面我们有提到,缓存是以namespace为单位的,不同namespace下的操作互不影响。
通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
在符合【Cache使用时的注意事项】的时候是没有危害的,那在什么情况下会产生危害呢?

例如在UserMapper.xml中有大多数针对user表的操作。但是在一个CustomerMapper.xml中,还有针对user单表的操作。
这会导致user在两个命名空间下的数据不一致。如果在UserMapper.xml中做了刷新缓存的操作,在CustomerMapper.xml中缓存仍然有效,如果有针对user的单表查询,使用缓存的结果可能会不正确。
并且当CustomerMapper.xml中的数据做了insert,update,delete等操作,会清空所在namespace下的全部缓存,那么很可能会影响到UserMapper.xml中的数据查询结果。

因此,当涉及到连表查询时,个人建议不要用到二级缓存!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值