Mybatis一级缓存和二级缓存原理与使用(mybatis的一级缓存)

前言

本文章将介绍Mybatis中的一级缓存和二级缓存。主要是围绕着下列问题去展开

  • 什么是一级缓存?
  • 为什么使用一级缓存?
  • 什么是二级缓存?
  • 二级缓存的作用?
  • 二级缓存是怎么实现的?

#java##数据库##开发#

一级缓存

Mybatis会在会话对象SqlSession对象中建议一个简单的缓存,将查询到的结果缓存起来,当下次查询的时候,会判断有没有一个完全一样的查询,如果有直接从缓存中返回。否则查询数据库并存入缓存,最后返回给用户。

这样做主要是避免我们在短时间内,反复地执行相同的查询语句,而得到的结果也是相同的。如果不使用缓存会造成很大的资源浪费。

一级缓存的原理及源码分析

  • 一级缓存默认就开启
  • 一级缓存底层是使用HashMap的数据结构存储
  • 缓存是在执行器Executor中创建,添加和删除缓存都在这里执行
  • 默认的缓存Key有五个部分组成(具体可以查看类:BaseExecutor):
  1. statementId: MapStatement的id
  2. offset:分页设置,默认为0,从RowBound分页对象中获取
  3. limit:分页最大页数,从RowBound分页对象中获取
  4. boundSql:查询的sql语句,从BoundSql对象中获取
  5. value:sql的参数,parameter中获取
  6. env: 当前的环境,如果sqlMapConfig.xml有配置<environment id="development">, 也会设置到key中

缓存相关的部分源码展示:

创建缓存KEY的源码

CacheKey cacheKey = new CacheKey(); //MappedStatement 的 id 。id就是Sql语句的所在位置包名+类名+ SQL名称 cacheKey.update(ms.getId()); // offset 就是 0 cacheKey.update(rowBounds.getOffset()); // limit 就是 Integer.MAXVALUE cacheKey.update(rowBounds.getLimit()); //具体的SQL语句 cacheKey.update(boundSql.getSql()); //后面是update 了 sql中带的参数 cacheKey.update(value); //...省略一千万行代码 if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); }

BaseExecutor部分query源码:

@Override public <E> List<E> query(MappedStatement ms, Object parameter,RowBounds rowBounds,ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); //创建缓存 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException{ //... 省略一千万行代码 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { //处理存储过程的作用 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //如果查不到的话,就从数据库查 queryFromDatabase() list = queryFromDatabase(ms, parameter,rowBounds,resultHandler,key,boundSql); } //... 省略一千万行代码 } /** * queryFromDatabase中,会对localcache进行写入。 * localcache对象的put方法最终交给Map进行存放 */ private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //执行查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { //清除缓存中的数据 localCache.removeObject(key); } //把查询到的数据,重新放入一级缓存中 localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }

二级缓存

二级缓存的原理和一级缓存的原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取;

  • 二级缓存需要手工开启;
  • 二级缓存存储的不是对象,而是对象的数据;
  • 因为二级缓存的存放介质很多,可以是内存,磁盘等。因为在取数据的时候需要反序列化操作,所以对象需要实现Serializable接口;
  • 二级缓存是基于mapper文件的namespace。多个sqlSession可以共享一个mapper中的二级缓存区域。

开启默认二级缓存的配置

开启的方式有以下三种:

1. 在全局配置文件sqlMapConfig.xml添加以下配置开启

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

2. 在Mapper.xml中添加配置开启

<!--开启二级缓存--> <cache></cache>

3. 在Mapper类中添加注解开启

//使用二级缓存,等用于<cache>属性 @CacheNamespace public interface UserMapper{ }

使用Redis实现二级缓存

  1. 使用默认的二级缓存实现有哪些弊端?
  2. 使用缓存中间件实现二级缓存解决了哪些问题?

使用默认的二级缓存实现,只能在单机系统中使用,不能实现分布式缓存。

引入缓存中间件redis作为二级缓存的存储介质,可以实现多机分布式缓存数据存储,对缓存数据进行集中管理。

  • 添加依赖。在pom.xml中添加以下依赖:
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency>
  • 在resources目录中添加配置 redis.properties

默认情况下,如果项目已经有使用了redis,且没有其他特殊配置,不配置redis.properties也是可以的。

redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0
  • 指定使用RedisCache作为二级缓存

xml中配置

<cache type="org.mybatis.caches.redis.RedisCache" />

注解中配置

@CacheNamespace(implementation=RedisCache.class) public interface UserMapper{ }

RedisCache源码分析

  1. 实现了Mybatis的Cache接口
  2. 内部使用jedis封装了对redis的CURD操作
  3. 使用了redis的Hash数据结构,系列化后存储数据
  4. RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建

部分核心源码:

//实现了Mybatis的Cache接口 public final class RedisCache implements Cache { public RedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require anID"); } this.id = id; //调用RedisConfigurationBuilder创建RedisConfig RedisConfig redisConfig = RedisConfigurationBuilder.getInstance() //RedisConfig核心方法,这个是加载解析redis配置文件的 .parseConfiguration(); pool = new JedisPool(redisConfig,redisConfig.getHost(),redisConfig.getPort(),redisConfig.getConnectionTimeout(),redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName()); } ... }
/** * 通过类加载器加载redis配置资源, */ public RedisConfig parseConfiguration(ClassLoader classLoader) { Properties config = new Properties(); InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename); if (input != null) { try { //加载配置信息到Properties容器中 config.load(input); }catch (IOException e) { throw new RuntimeException( "An error occurred while reading classpath property '" + redisPropertiesFilename + "', see nested exceptions", e); } finally { try { input.close(); } catch (IOException e) { // close quietly } } } //redisConfig继承了 JedisPoolConfig RedisConfig jedisConfig = new RedisConfig(); //开始设置redis配置 setConfigProperties(config, jedisConfig); return jedisConfig; }

结语

实际情况中,我个人是比较少直接用到mybatis的二级缓存的。都是自己使用redis缓存在业务上做单独的缓存处理(可能是我项目经验不够吧[奸笑])。但是总体来说,其实也是根据系统开发者的想法+业务本身的各种因素去决定的。合适才是最重要的。

前言中的问题,不知道大家是否已经能知道心里的答案呢?可以评论中写出来巩固自己的理解,也能帮助其他小伙伴哦[憨笑]

[玫瑰][玫瑰]?你的“关注”“收藏”是我最大的动力。非常感谢。[玫瑰][玫瑰]

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注