解析mybatis一级缓存与二级缓存的原理

1.mybatis中的缓存是在mybatis框架中的Executor中来实现的,我们来看一下Executor的继承图

2.通过以上类图我们可以发现Executor接口下有两大实现类BaseExecutor与CachingExecutor

(1)BaseExecutor(用来存储我们的一级缓存)

@Override
  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++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;  //会先去localCache中去查找我们的数据
      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();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

(2)CachingExecutor(是装饰器模式的实现,用来查询我们的二级缓存)

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();    //在MappedStatement中去获取二级缓存的类型。
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key); //通过tcm来查询缓存,而tcm是CachingExecutor中的变量TransactionalCacheManager
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //delegate为CachingExecutor中保存的BaseExecutor的引用,若二级缓存不存在回去调用BaseExecutor的方法。
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }

给大家免费分享一套资料,这套资料包含面试题,视频,简历模>板,成长路径,核心总结文档,需要这份资料的小伙伴,点击这里即可免费领取,备注暗号 CSDN

3.当我们开启二级缓存后Mybatis会使用CachingExecutor去装饰我们的BaseExecutor,所以会先查询二级缓存后再去查询一级缓存。

Configuration中的newExecutor方法public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {    //判断Executor的类型
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {                          //如果二级缓存开启则使用CachingExecutor去装饰我们的executor;
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

4.一级缓存与二级缓存的作用范围

(1)一级缓存(由于一级缓存是存在BaseExecutor中的,而Executor又作为创建SqlSession的参数,因此一级缓存具有和sqlsession一样的生命周期)

这是在SqlSessionFactory中调用openSession()中调用的方法private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);  //新建一个Executor作为参数传给DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

(2)二级缓存(使用CachingExecutor来装饰)

//CachingExecutor中的query方法@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();        //由此可见我们的二级缓存并不是储存在CachingExecutor中的,而是从MappedStatement中去获取。因此mybatis的二级缓存的生命周期为mapper级别的
    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) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis缓存分为一级缓存二级缓存,下面分别介绍它们的实现方式并给出相应的Java代码。 1. 一级缓存 一级缓存是指在MyBatis的SqlSession对象中缓存查询结果,它的生命周期与SqlSession对象相同。当我们发起一个查询请求时,MyBatis会先检查SqlSession的一级缓存中是否存在相应的查询结果,如果存在,则直接返回缓存中的结果,否则执行SQL语句并将查询结果存入缓存中。下面是一级缓存Java代码: ```java // 获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { // 获取Mapper接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 查询用户信息 User user1 = userMapper.selectById(1); User user2 = userMapper.selectById(1); // 由于是同一次SqlSession操作,所以user1和user2引用的是同一个对象 System.out.println(user1 == user2); // true } finally { sqlSession.close(); } ``` 在上面的代码中,我们先获取了SqlSession对象,然后获取了一个Mapper接口,接着发起了两次相同的查询请求,由于是同一次SqlSession操作,所以user1和user2引用的是同一个对象,打印结果为true。 2. 二级缓存 二级缓存是指在MyBatis的SqlSessionFactory中缓存查询结果,它的生命周期与整个应用程序相同。当我们发起一个查询请求时,MyBatis会先检查二级缓存中是否存在相应的查询结果,如果存在,则直接返回缓存中的结果,否则执行SQL语句并将查询结果存入缓存中。下面是二级缓存Java代码: ```java // 获取SqlSession对象 SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); try { // 获取Mapper接口 UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); // 查询用户信息 User user1 = userMapper1.selectById(1); sqlSession1.commit(); // 提交事务,将查询结果存入二级缓存 User user2 = userMapper2.selectById(1); // 由于是两个不同的SqlSession对象,所以user1和user2引用的是不同的对象 System.out.println(user1 == user2); // false } finally { sqlSession1.close(); sqlSession2.close(); } ``` 在上面的代码中,我们先获取了两个不同的SqlSession对象,接着获取了两个相同的Mapper接口,然后发起了两次相同的查询请求,由于是两个不同的SqlSession对象,所以user1和user2引用的是不同的对象,打印结果为false。需要注意的是,由于二级缓存是与整个应用程序相关的,所以在多个应用程序之间共享二级缓存时需要注意缓存的有效性和并发性问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值