Mybatis源码分析系列(一)-mybatis中的一级缓存

目录

1.一级缓存的使用

2.走进源码的世界

1.get的过程

2.put的过程

3.key的生成策略

4.总结


下一篇:mybatis二级缓存源码分析


1.一级缓存的使用

        说起一级缓存的使用,其实简单至极。mybatis默认开启,我们甚至不需要任何的配置。闲言少叙来看一段代码:

public class Application {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        String res = sqlSession.selectOne("com.mybatis.demo.CronMapper.selectOne");
        String res2 = sqlSession.selectOne("com.mybatis.demo.CronMapper.selectOne");
        //结果为true
        System.out.println(res==res2);
    }
}

我想这段代码简单的不能再简单了吧,这是mybatis不借助spring的最基本用法。我们通过会话sqlSession完成两次一模一样的查询,那么第二次查询就不会去查数据库了,而是直接从一级缓存中获取。注意此时我们先不考虑二级缓存的事情,下篇文章会介绍,不要着急!

        

2.走进源码的世界

        接下来,我们跟着debug断点,一步步的走进mybatis的源码。不过捏,不用紧张,一级缓存简单的不敢想象,不信咱们走起!

1.get的过程

public class Application {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //进入这个方法
        String res = sqlSession.selectOne("com.mybatis.demo.CronMapper.selectOne");
        String res2 = sqlSession.selectOne("com.mybatis.demo.CronMapper.selectOne");
        System.out.println(res==res2);
    }
}

sqlSession的默认实现是DefaultSqlSession。于是我们进入到这个方法:

@Override
  public <T> T selectOne(String statement) {
    //进入这个方法
    return this.selectOne(statement, null);
  }

进到这个方法:

@Override
public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    //进入这个方法
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
}

进到这个方法:

@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    //进入这个方法
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

进到这个方法:

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      //进入这个方法
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

上边的代码都很简单,一步步跟就是了。接下来稍微复杂点了。此时这个executor默认实现是CachingExecutor。于是我们进入到CachingExecutor的query方法:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //进入这个方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    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) {
          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);
  }

这里需要注意下,这个delegate的默认实现是SimpleExecutor,但是SimpleExecutor中并没有重写query方法,而是直接使用其父类BaseExecutor的query方法。所以此时我们会进入到BaseExecutor的query方法。OK 继续!

@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++;
      //这个三目运算会进入第一个分支
      //localCache.getObject(key) 这行代码就是从一级缓存中获取数据
      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();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

那么这个localCache是个啥呢?其实他就是BaseExecutor的一个属性,类型是PerpetualCache。而在PerpetualCache里边维护了一个Map,我们来看看PerpetualCache的代码:

public class PerpetualCache implements Cache {

  private final String id;

  //真正的一级缓存 其实就是个Map
  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

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

  @Override
  public int getSize() {
    return cache.size();
  }

  //向一级缓存中存放数据
  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  //从一级缓存中获取数据
  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }
}

行棋至此,我们已经看过了从一级缓存中查询数据的总体流程。我们来回顾总结一下:

首先调用sqlSession查询方法(默认实现是DefaultSqlSession)

---->调用executor的查询方法(executor的默认实现是CachingExecutor)

---->调用delegate的查询方法(delegate的默认实现是SimpleExecutor,但是没有重写query方法,于是直接使用其父类BaseExecutor的query方法)

---->BaseExecutor中维护了一个属性localCache,类型为PerpetualCache。

---->PerpetualCache里边维护了一个Map,是真正的一级缓存。存取都是操作这个map而已。

2.put的过程

看过了从一级缓存中获取,那么是什么时候将数据库查询结果放到缓存中的呢?我们再来看看BaseExecutor的query方法(上边也提到了):

@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;
      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;
  }

进到这个方法:

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;
  }

进到这个方法:

@Override
  public void putObject(Object key, Object value) {
    //这个cache就是那个Map
    cache.put(key, value);
  }

存放的过程更简单是吧,其实就是直接放到了PerpetualCache的Map里边而已。

3.key的生成策略

        可能有好多小伙伴会问,我们知道了数据存在哪儿,什么时候存什么时候取。那缓存的key是怎么生成的呢?和哪些因素有关呢?或者说影响缓存命中率的因素有哪些?接下来我们看看:

978273079:-578870450:com.mybatis.demo.CronMapper.selectOne:0:2147483647:select cron from public.cron where id = ?:1:development

这个就是一个key,我们可以拆开来看看每个部分分别代表什么?

(1) 978273079:hashCode 我们可以不用管,不会影响缓存的命中与否。

(2) -578870450:这货是checkSum,具体什么作用我也不知道,但是不会影响命中与否可以不管。

(3) com.mybatis.demo.CronMapper.selectOne:这个是MappedStatementId,也就是我们mapper中的方法的全限定名即包名.类名.方法名。

(4) 0:分页的偏移量offset

(5) 2147483647:每页大小,因为我没有设置分页参数,所以是默认值Integer.MaxValue

(6) select cron from public.cron where id = ?:1:这个很明显就是我们的sql语句,包括参数。

(7) development:这个是我们配置的环境id,一般配置在xml里边,如下:

<environments default="development">
        //就是这个货
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="org.postgresql.Driver"/>
                <property name="url" value="jdbc:postgresql://localhost:5432/postgres"/>
                <property name="username" value="postgres"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

4.总结

        4.1 执行流程:首先查询缓存,存在直接返回。不存在查询数据库然后放到缓存供下次查询使用。

        4.2 影响一级缓存命中的因素:

                1.MappedStatementId

                2.sql+参数

                3.分页条件

                4.环境(通常这个不会有变化)

                5.这个也是最重要的,必须是同一个会话即同一个sqlSession。通过代码我们可以看到,一个会话对应一个CachingExecutor,一个CachingExecutor对应一个BaseExecutor,一个BaseExecutor对应一个PerpetualCache。简单说就是一个sqlSession对应一个缓存。如果会话不同那么缓存也必定不同。我们常说一级缓存不能跨线程正是这个原因。一般情况下一个线程就会创建一个会话,那么一级缓存也一定是不同的。      

        好了一级缓存完结,下一篇二级缓存!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值