Mybatis的一二级缓存源码分析

Mybatis的一二级缓存源码分析

一级缓存

介绍

同一个sqlSession或者statement里面,如果请求的语句参数一样,就会命中缓存直接返回,不会再去查询数据库。一旦数据库有修改,缓存则会失效。

mybatis:
    configuration:
      local-cache-scope: session ##一级缓存范围 有session和statement

原理讲解

测试代码:

@Test
public void test2(){
    SqlSession session = sqlSessionFactory.openSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    System.out.println(mapper.getUserVoByUserId(String.valueOf(200001)));
    System.out.println(mapper.getUserVoByUserId(String.valueOf(200001)));
}

返回结果:

15:50:37.525 [main] DEBUG c.r.c.m.UserMapper - [getObject,60] - Cache Hit Ratio [com.ruoyi.customer.mapper.UserMapper]: 0.0
15:50:37.525 [main] DEBUG c.r.c.m.U.getUserVoByUserId - [debug,137] - ==>  Preparing: select tu.`user_id`, tu.`user_code`, tu.`customer_id`, tu.`user_name`, tu.`password`, tu.`type`, tu.`status`, tu.`encrypt_type`, tu.`encrypt_key`, tu.`hash_type`, tu.`post_num`, tu.`remark`, tu.create_by, tu.create_time, tu.update_by, tu.update_time, tc.simple_name `customer_simple_name`, tc.full_name `customer_full_name`, tc.area `area`, sda.dict_label `area_name`,sds.dict_label `status_name`, sdt.dict_label `type_name` from t_user tu left join t_customer tc on tu.customer_id = tc.customer_id left join sys_dict_data sda on tc.area = sda.dict_value left join sys_dict_data sds on tc.status = sds.dict_value left join sys_dict_data sdt on tc.type = sdt.dict_value WHERE sds.dict_type = 't_customer_status' and sda.dict_type = 'sys_zoon' and sdt.dict_type = 't_customer_type' and tu.user_id = ?
15:50:37.532 [main] DEBUG c.r.c.m.U.getUserVoByUserId - [debug,137] - ==> Parameters: 200001(String)
15:50:37.567 [main] DEBUG c.r.c.m.U.getUserVoByUserId - [debug,137] - <==      Total: 1
UserVo(customerSimpleName=xxx, customerFullName=xxx, typeName=内部, statusName=正常, area=4, areaName=北区)
15:50:37.568 [main] DEBUG c.r.c.m.UserMapper - [getObject,60] - Cache Hit Ratio [com.ruoyi.customer.mapper.UserMapper]: 0.0
UserVo(customerSimpleName=xxx, customerFullName=xxx, typeName=内部, statusName=正常, area=4, areaName=北区)

如上所示只进行了一次查询,我们可以将返回的对象进行比较,会发现是同一个对象。

有三个基本的类:
SqlSession: 用户与底层数据库的交互代码
Executor: SqlSession操作数据库的代码
Cache: 提供缓存的功能

/*
SqlSession
    -> DefaultSqlSession
    -> MybatisSqlSessionTemplate
    -> SqlSessionManager
    -> SqlSessionTemplate
*/
public interface SqlSession extends Closeable {

  //根据statement查询出一条数据
  <T> T selectOne(String statement);

  //根据statement和parameter查询出一条数据
  <T> T selectOne(String statement, Object parameter);

  //根据statement查询出多条数据
  <E> List<E> selectList(String statement);

  //根据statement和parameter查询出多条数据
  <E> List<E> selectList(String statement, Object parameter);

  //根据statement和parameter和rowBounds界限查询出多条数据
  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
  <K, V> Map<K, V> selectMap(String statement, String mapKey);
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
  <T> Cursor<T> selectCursor(String statement);
  <T> Cursor<T> selectCursor(String statement, Object parameter);
  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
  void select(String statement, Object parameter, ResultHandler handler);
  void select(String statement, ResultHandler handler);
  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  int insert(String statement);
  int insert(String statement, Object parameter);
  int update(String statement);
  int update(String statement, Object parameter);
  int delete(String statement);
  int delete(String statement, Object parameter);
  void commit();
  void commit(boolean force);
  void rollback();
  void rollback(boolean force);
  List<BatchResult> flushStatements();
  @Override
  void close();
  void clearCache();
  Configuration getConfiguration();
  <T> T getMapper(Class<T> type);
  Connection getConnection();
}

/*
Executor
    -> BaseExecutor
        -> BatchExecutor
        -> ClosedExecutor
        -> ReuseExecutor
        -> SimpleExecutor
    -> CachingExecutor
*/
public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

/*
Cache
    -> PerpetualCache
    -> BlockingCache
    -> FifoCache
    -> LoggingCache
    -> LruCache
    -> PerpetualCache
    -> ScheduledCache
    -> SerializedCache
    -> SoftCache
    -> SynchronizedCache
    -> TransactionalCache
    -> WeakCache
*/
public interface Cache {
  String getId();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  int getSize();
  default ReadWriteLock getReadWriteLock() {
    return null;
}

以上的类就是实现一二级缓存的主要基类,下面我们DEBUG来讲解一级缓存一下。
DefaultSqlSessionFactory#openSession()
-> DefaultSqlSessionFactory#openSessionFromDataSource()
-> 创建Executor;final Executor executor = configuration.newExecutor(tx, execType);
-> Configuration#newExecutor()
-> 根据executor选型创建,默认是ReuseExecutor
-> 如果cacheEnabled是true,则使用装配器模式,构建CacheExecutor
-> 创建SqlSession; new DefaultSqlSession(configuration, executor, autoCommit)
MapperMethod#execute()执行方法
-> DefaultSession#selectOne()
-> DefaultSession#selectList()
-> BaseExecutor#query()

    
    // BaseExecutor#query
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //从Cache中获取缓存内容
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //如果缓存中的内容为空,则执行
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      //如果缓存级别为statement,则清空当前缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

优缺点

  • 读取速度快
  • 一级缓存的里面缓存cache只是一个HashMap,没有对其做容量、分类限制,缓存数据过多对性能有所影响。
  • 一级缓存的范围是Session,如果系统部署是分布式的,同时对数据库某张表有操作,会造成脏数据的读取

二级缓存

介绍

多个Session之间可以进行共享的缓存,使用CachingExecutor装置BaseExecutor,先进行二级缓存,再进行一级缓存。

原理讲解

如上面所说,如果cacheEnabled是true(二级缓存开启),则使用装配器模式,构建CacheExecutor
下面就是先走 CachingExecutor#query,再走BaseExecutor#query

    // CacheExecutor#query
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //先看一下mapper有没有缓存,如果mapper都没有缓存,那么相同操作的缓存更不可能存在
    Cache cache = ms.getCache();
    if (cache != null) {
      //如果mapperStatement需要的话,就刷新缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //查看二级缓存事务中中有没有,如果有的话就需要再执行下面的
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          //如果事务缓存中没有,则执行装饰器中的query,一般进入BaseExecutor#query
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //将查询结果放去缓存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

优缺点

  • 多表关联查询的时候,容易出现脏数据
  • 分布式的时候需要采用redis、memcached来作为缓存

实现redis来作为mybatis的二级缓存

public class MybatisRedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private String id;

    private RedisTemplate redisTemplate;

    public MybatisRedisCache(String id) {
        if (id == null){
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.info("Redis Cache id " + id);
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        RedisTemplate redisTemplate = getRedisTemplate();
        if (value != null){
            redisTemplate.opsForValue().set(key.toString(),value,2, TimeUnit.DAYS);
        }
    }

    @Override
    public Object getObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        if (key != null){
            return redisTemplate.opsForValue().get(key.toString());
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        if (key != null){
            redisTemplate.delete(key.toString());
        }
        return null;
    }

    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        Set keys = redisTemplate.keys("*" + this.id + "*");
        if (!CollectionUtils.isEmpty(keys)){
            redisTemplate.delete(keys);
        }
    }

    @Override
    public int getSize() {
        return 0;
    }
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null){
            redisTemplate = SpringUtils.getBean("redisTemplate");
        }
        return redisTemplate;
    }
}

//最后在需要使用缓存的mapper.xml文件配置一下
/*
 <cache type="com.xxx.xxx.utils.MybatisRedisCache">
            <property name="eviction" value="LRU" />
            <property name="flushInterval" value="6000000" />
            <property name="size" value="1024" />
            <property name="readOnly" value="false" />
</cache>
*/

如果喜欢博主,可以关注我的公众号哈
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值