Mybaties中的缓存机制,是面试常出现的问题:包括一级缓存.二级缓存,集成二级缓存框架:(这里介绍redis):
一级缓存的剖析和源码:
1:Mybaties的一级缓存是默认开启的,作用范围是在同一个sqlsession中:sqlsession调用相同的查询方法时:会向缓存集合map中查询有没有缓存结果集:如果有直接取出:如果没有,查询数据库,并把查询结果封装到map中:key的值有6个部分组成:
一级缓存的xml更改方式:默认是开启的,不建议配置:
<setting name="localCacheScope" value="SESSION"/>
通过代码的方式来查看查询流程:图和代码:
public void testLocalCacheScope() 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("studentMapper读取数据: " + studentMapper.getStudentById(1));
System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "个学生的数据");
System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
控制台查看:
第一次查询是从数据库查询的:第二次查询是从缓存中查询的:在进行了dml语句后事务会自动提交:这时发现控制台又从新从数据库来进行查询:
一个SqlSession会持有一个Executor执行器,复杂查询缓存的维护
DefaultSqlSession持有BaseExecutor,BaseExecutor聚合了PerpetualCache:点开发现是个map集合:
执行器家族:负责缓存的查询维护:
注意:缓存类实现了Cache缓存类:
既然是个map:那key值是什么呢:Statement Id + Offset + Limmit + Sql + Params
在执行器中会创建key:
在执行query方法时:会先从缓存类中获取这个map的value:查看是不是为空:为空的话执行查询数据库方法:将value存到map中:
代码的最后:会进行配置文件的判断所以如果不需要缓存可以在mybatis的配置文件设置localCacheScope=statement
关于一级缓存清空的问题:在使用query.update方法时,会调用cleanCache()方法,来对缓存数据进行清除:
二级缓存:
二级缓存作用域:mapper.xml文件:也就是说只要是基于这个文件中的sql查询,都会进行缓存:
开启二级缓存:<setting name="cacheEnabled" value="true"/>config.xml文件中: mapper.xml文件中:配置:<cach>
配置属性
type:引入自定义的缓存类(需要写全类名)
eviction:清除策略,例如先进先出FIFO,最近最少使用LRU等
flushInterval:刷新间隔,以毫秒为单位
size:引用大小,最多可存储多少个结果集的引用(可以为任意正数)
readOnly:设置为只读,只读的缓存会给所有调用者返回缓存对象的相同实例,可读写的缓存会(通过序列化)返回缓存对象的拷贝,默认为false,速度慢但是更安全
blocking:是否使用阻塞缓存,默认为false,当指定为true时将采用BlockingCache进行封装,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁这样可以阻止并发情况下多个线程同时查询数据
二级缓存是Application级的缓存,一级缓存缓存的是SQL语句,而二级缓存缓存的是结果对象,二级缓存会存储一级缓存的内容
数据会先存入一级缓存中,二级缓存会从一级缓存中获取数据:
当开启了二级缓存后,我们再查询的时候,这时候的Executor实现类就是CachingExecutor了。源码实现
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//从MappedStatement中获取二级缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//从缓存中获取数据
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//如果获取不到,则会调用BaseExecutor的query方法
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//放入TransactionalCacheManager中,实际上是放入TransactionalCache这个对象的一个map容器中,然后在sqlsession提交或者close的时候更新二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
二级缓存的查询逻辑大概流程就是先从二级缓存中获取,如果获取不到则调用BaseExecutor的query方法,如果开启了一级缓存,就再从一级缓存查询,否则从数据库查询数据;放入一级缓存中:最后将缓存数据存入TransactionalCache对象的map容器中,等待sqlsession提交或者close再更新到二级缓存:也就是说二级缓存必须要提交事务,或是关闭sqlsession后才会生效:原因如下:
//把缓存put到TransactionalCache的map容器中 getTransactionalCache(cache).putObject(key, value);
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
//添加到缓存对象中,最终缓存对象也是使用个map来存储
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
二级缓存的主要构建过程就是在初始化时先获取mapper文件中cache标签,并得到标签中设置的属性,然后通过建造者模式构建缓存对象,在具体的构建过程中又是通过装饰者模式实现根据不同的属性组合,优雅的为二级缓存添加相应属性功能。
二级缓存失效策略
1、二级缓存失效同一级缓存一样,增删改都会造成缓存清除
2、默认情况不是同一个namespace也不会走同一个缓存
3、自身属性配置的刷新时间和淘汰策略。
Mybatis集成redis
redis常用类
1.1 Jedis
jedis就是集成了redis的一些命令操作,封装了redis的java客户端
1.2 JedisPoolConfig
Redis连接池
1.3 ShardedJedis
基于一致性哈希算法实现的分布式Redis集群客户端
实现 mybatis 的二级缓存,一般来说有如下两种方式:
1) 采用 mybatis 内置的 cache 机制。
2) 采用三方 cache 框架, 比如ehcache, oscache 等等.
在集群环境下使用mybaties的二级缓存:
这里只讲一下配置方式和原理:
首先作为二级缓存框架:需要事项cach类:mybaties自定义了redisCach的接口:
同样是需要在配置文件中开启缓存:
<settingname="cacheEnabled"value="true"/>
edis会自动的将Sql+条件+Hash等当做key值,而将查询结果作为value,只有请求中的所有参数都符合,那么就会使用redis中的二级缓存。其查询结果如下: