zoukankan      html  css  js  c++  java
  • mybatis源码解析( 二级缓存)

    一、二级缓存介绍

    1、一级缓存

      Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言,属于会话级缓存,使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。会将对象缓存起来,如果修改对象,缓存中对象也会被修改。

    一级缓存的使用条件:

    1. 必须是相同的SQL和参数
    2. 必须是相同的会话
    3. 必须是相同的namespace 即同一个mapper
    4. 必须是相同的statement 即同一个mapper 接口中的同一个方法
    5. 查询语句中间没有执行session.clearCache() 方法
    6. 查询语句中间没有执行 insert update delete 方法(无论变动记录是否与 缓存数据有无关系)

    2、二级缓存

    二级缓存默认是不开启的,需要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。缓存中存储的是序列化之后的,所以不同的会话操作对象不会改变缓存

    二级缓存使用条件:

    1. 当会话提交或关闭之后才会填充二级缓存
    2. 必须是在同一个命名空间之下
    3. 必须是相同的statement 即同一个mapper 接口中的同一个方法
    4. 必须是相同的SQL语句和参数
    5. 如果readWrite=true ,实体对像必须实现Serializable 接
    6. 任何一种增删改操作 都会清空整个namespace 中的缓存
    7. 多表操作不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。

    二级缓存开启

    <settings>
        <setting name = "cacheEnabled" value = "true" />
    </settings>

    还需要在 Mapper 的xml 配置文件中加入 <cache>标签

    cache 标签有多个属性,一起来看一些这些属性分别代表什么意义

    • eviction: 缓存回收策略,有这几种回收策略
      • LRU - 最近最少回收,移除最长时间不被使用的对象
      • FIFO - 先进先出,按照缓存进入的顺序来移除它们
      • SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
      • WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
    • flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值
    • readOnly: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改
    • size : 缓存存放多少个元素
    • type: 指定自定义缓存的全类名(实现Cache 接口即可)
    • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

    二、源码解析

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
      }
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
    //insert方法
    case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; }
    //更新
    case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; }
    //删除
    case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; }
    //查询
    case SELECT:
    //不同返回类型的处理
    if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
    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.<T>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;
        }
      }
    
     public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
      }
    
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
    //获取MappedStatement 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(); } }
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取sql BoundSql boundSql
    = ms.getBoundSql(parameterObject);
    //获取一级缓存二级缓存的key 例:-609361858:2327171568:com.pjf.mybatis.dao.UserMapper.selectById:0:2147483647:select * from user where id=?:1:SqlSessionFactoryBean CacheKey key
    = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //获取mybatis的二级缓存配置<cache> 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.<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); }
    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; }

    再来看下更新操作,什么时候清空缓存的

    public int update(String statement, Object parameter) {
        try {
          dirty = true;
          MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.update(ms, wrapCollection(parameter));
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    //清空二级缓存 flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject); }
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
    //在执行更新之前,清空一级缓存 clearLocalCache();
    return doUpdate(ms, parameter); }
  • 相关阅读:
    variant conversion error for variable:v8
    oracle第二步创建表空间、用户、授权
    Java WEB 乱码解决大全
    跳转的两种方式(转发与重定向)
    jsp页面中 <%%> <%! %>, <%=%> <%-- --%>有什么区别
    Web.xml中Filter过滤器标签几个说明
    SSH面试题(struts2+Spring+hibernate)
    做一个java项目要经过那些正规的步骤
    web.xml 配置中classpath: 与classpath*:的区别
    Web.xml配置详解之context-param
  • 原文地址:https://www.cnblogs.com/pjfmeng/p/14212645.html
Copyright © 2011-2022 走看看