Mybatis-Plus版本3.4.3
为啥要提前申明下版本,因为该文章是基于Mybatis-Plus3.4.3 版本源码做的分析,有一些源码与以前旧版本有不一样的地方,没有说明版本的话会引导初学者迷惑。有的说1有的说2 不知道该看那个文章才是适用自己的,不管怎么,大致思想不会变化,最好建议大家学会使用后多看源码。万变不离其中。
基本缓存问题
- 什么是缓存?
1.存在内存中的临时数据
2.将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库 数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 为什么使用缓存
减少和数据库的交互次数,减少系统开销或IO,提高系统效率
- 什么样的场景使用缓存?
经常查询同时不经常修改的数据。
Mybatis 缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地 提升查询效率。
一级缓存
一级缓存:也称为本地缓存,基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为SqlSession,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,各个SqlSession之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis默认打开一级缓存、不允许关闭。
二级缓存(默认是开启)
也称为全局缓存,是mapper级别的缓存。二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,所以默认也是本地缓存。不同之处在于其存储作用域为 Mapper(Namespace),可以在多个SqlSession之间共享,是针对一个表的查结果的存储,可以共享给所有针对这张表的查询的用户。也就是说对于mapper级别的缓存不同的sqlsession是可以共享的,并且可自定义存储源,如 Ehcache、Redis。默认开启二级缓存,但是还需要配置才可以使用。
查询流程
一级缓存流程图
演示代码
InputStream inputStream = Resources.getResourceAsStream(XML);
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSessionOne = sqlSessionFactory.openSession(false);
SqlSession sqlSessionTwo = sqlSessionFactory.openSession(false);
MpUserMapper sessionOneMapperOne = sqlSessionOne.getMapper(MpUserMapper.class);
MpUser mpUser = sessionOneMapperOne.selectById(1L);
MpUser mpUser2 = sessionOneMapperOne.selectById(1);
二级缓存流程图
演示代码
InputStream inputStream = Resources.getResourceAsStream(XML);
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSessionOne = sqlSessionFactory.openSession(false);
SqlSession sqlSessionTwo = sqlSessionFactory.openSession(false);
MpUserMapper sessionOneMapperOne = sqlSessionOne.getMapper(MpUserMapper.class);
MpUserMapper sessionTwoMapperMapperOne = sqlSessionTwo.getMapper(MpUserMapper.class);
MpUser mpUser = sessionOneMapperOne.selectById(1L);
sqlSessionOne.commit();
MpUser mpUser2 = sessionTwoMapperMapperOne.selectById(1);
整体流程图
缓存源码分析
一级缓存查询
- 获取执行器设置到Sessioon
- 获取代理Mappper
- 查询分析
二级缓存查询
- 首先配置二级缓存的存储位置我这次选择的是Redis 大家可以按照自己需求来选择,默认不建议,因为他也是本地的缓存,分布式情况还需要再次请求数据库缓存到本地。
public class RedisCache implements Cache {
private final String id;
private static Jedis cache;
static {
cache = new Jedis("127.0.0.1", 6379);
cache.auth("1111111");
}
public RedisCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
/**
* 返回缓存所有键值对的数量
*
* @return
*/
@Override
public int getSize() {
Long dbSize = cache.dbSize();
return dbSize.intValue();
}
/**
* 向缓存中存入数据
*
* @param key
* @param value
*/
@Override
public void putObject(Object key, Object value) {
byte[] keyBs = SerializationUtils.serialize((Serializable) key);
byte[] valueBs = SerializationUtils.serialize((Serializable) value);
cache.set(keyBs, valueBs);
}
/**
* 从缓存中获取数据
*
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
byte[] keyBs = SerializationUtils.serialize((Serializable) key);
byte[] valueBs = cache.get(keyBs);
if (valueBs != null) {
return SerializationUtils.deserialize(valueBs);
}
return null;
}
/**
* 清除缓存
*
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
byte[] keyBs = SerializationUtils.serialize((Serializable) key);
byte[] valueBs = cache.get(keyBs);
Object obj = SerializationUtils.deserialize(valueBs);
cache.del(keyBs);
return obj;
}
/**
* 清空缓存
*/
@Override
public void clear() {
cache.flushDB();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
我使用的注解形式
@CacheNamespace(implementation = RedisCache.class,flushInterval = 1)
目前支持两种模式一种是在xml 配置 一种上面注解模式,为啥两种可以通过源码分析得出。
这里简答说下上面截图就是设置二级缓存cache 对象的位置,可以发现一种是xml 解析赋值,一种注解解析赋值。
最后切记缓存的实体对象需要实例化。
总结:缓存这里细节也很多,也有使用不当造成异常问题或者性能问题。特别是并发情况的嵌套查询。后面分析嵌套查询的延迟加载,以及为啥一级缓存要最开始要加占位符。