手把手教你阅读mybatis核心源码,掌握底层工作原理与设计思想

Posted 1994july

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你阅读mybatis核心源码,掌握底层工作原理与设计思想相关的知识,希望对你有一定的参考价值。

Mybatis目前作为互联网公司Java体系开源ORM框架的首选,它有着天然的优势,很多同学只关注其公司业务CRUD程序的编写,忽略了其源码阅读的重要性。下面来看一段使用Mybatis API写的代码示例:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
    BusinessMapper mapper = session.getMapper(BusinessMapper.class);
    Business business = mapper.selectBusinessById(1);
    System.out.println(business);
    
}finally {
    session.close();
}

接下来按照示例代码的步骤一步一步地来分析代码的运行背后的秘密,揭开mybatis源码的真实面目。给出的源码片段均有中文注释,方便同学们加深理解。

一、全局配置解析过程

1.1 SqlSessionFactoryBuilder(构建工厂类)

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

创建一个新的SqlSessionFactoryBuilder对象,这里使用了建造者模式。调用了build()方法创建了SqlSessionFactory对象,在SqlSessionFactoryBuilder中有9个重载的build()方法,可以使用不同的方式来创建SqlSessionFactory对象,其默认是单例模式的。

技术图片

1.2 XmlConfigBuilder(解析全局配置文件)

创建XmlConfigBuilder对象用来解析全局配置文件,解析完成之后会返回一个Configuration对象。

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      //1、创建XMLConfigBuilder对象
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //2、调用解析方法返回Configuration对象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

XMLConfigBuilder继承自抽象类BaseBuilder,解析全局的配置文件,BaseBuilder还有一些子类,用来创建不同的目标的。例如:

  • XMLMapperBuilder:解析Mapper映射器
  • XMLStatementBuilder:解析增删改查标签
  • XMLScriptBuilder:解析动态SQL

技术图片

接着来看下XMLConfigBuilder对象调用的parse()方法的源码:

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // XPathParser,dom 和 SAX 都有用到 >>
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

java中解析xml配置文件的方式有很多种,mybatis对DOM和SAX两种方式做了不同的封装。接着看parseConfiguration()方法。

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 对于全局配置文件各种标签的解析
      1、解析<properties>标签,可以读取外部引入的属性文件,比如database.properties
      propertiesElement(root.evalNode("properties"));
      // 2、解析 settings 标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //3、获取Virtual File System自定义实现类,比如读取本地文件
      loadCustomVfs(settings);
      //4、根据<longImpl>标签获取日志实现类
      loadCustomLogImpl(settings);
      //5、解析类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //6、解析plugins标签,比如翻页插件PageHelper
      pluginElement(root.evalNode("plugins"));
      // 用于创建对象
      objectFactoryElement(root.evalNode("objectFactory"));
      // 用于对对象进行加工
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 反射工具箱
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // settings 子标签赋值,默认值就是在这里提供的 >>
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 创建了数据源 >>
      environmentsElement(root.evalNode("environments"));
      // 解析databaseIdProvider标签,生成DatabaseIdProvider对象
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 用来做映射的,得到JavaType和JdbcType,存放在TypeHandlerRegistry对象中
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析引用的Mapper映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

上面的方法中所有的值都会封装到Configuration对象中。下面是创建过程的时序图。 技术图片

二、会话创建过程

SqlSession session = sqlSessionFactory.openSession();

这里实际上调用了DefaultSqlSessionFactory类的openSessionFromDataSource()方法。

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);
      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.1 获取Environment对象

从Configuration对象中获取Environment对象,环境对象中有事务工厂类;

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
}

2.2 创建事务

从Environment对象中获取一个TranscationFactory对象,事务工厂类型可以配置成JDBC或者MANAGED。

  • JDBC;使用jdbc的Connection对象来管理事务;
  • MANAGED:事务将有容器进行管理。
 private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
 }

2.3 创建执行器

final Executor executor = configuration.newExecutor(tx, execType);

执行器Executor的基本类型有三种:

  • SIMPLE(默认)
  • BATCH
  • REUSE
if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 默认 SimpleExecutor
      executor = new SimpleExecutor(this, transaction);
}

技术图片

抽象类BaseExecutor实现Executor接口,这是模板设计模式的体现。

技术图片

缓存装饰

在newExecutor()方法中:

// 二级缓存开关,settings 中的 cacheEnabled 默认是 true
if (cacheEnabled) {
  executor = new CachingExecutor(executor);
}

代理插件

// 植入插件的逻辑,至此,四大对象已经全部拦截完毕
executor = (Executor) interceptorChain.pluginAll(executor);

2.4 返回SqlSession

SqlSession类中包括Configuration和Executor两大对象。下面是创建过程的时序图。

技术图片

三、获取代理对象

接口中的名称和Mapper.xml文件中的namespace是一一对应的,方法名称也是StatementId也是对应的。

BusinessMapper mapper = session.getMapper(BusinessMapper.class);
Business business = mapper.selectBusinessById(1);
<mapper namespace="com.sy.mapper.BusinessMapper">

<select id="selectBusinessById" resultMap="BaseResultMap" statementType="PREPARED" >
        select * from bsuiness where bid = #{bid}
    </select>

3.1 getMapper()方法

1、DefaultSqlSession中的**getMapper()**方法:

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

2、Configuration类中的getMapper()方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

3、MapperRegistry中的getMapper()方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到一个map中,获取Mapper的代理对象,实际上是从map中获取对应的工厂类后,最终通过JDK动态代理创建的。

protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器;
    // 2:被代理类实现的接口;
    // 3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

MapperProxy实现了InvocationHandler接口,参数有sqlSession, mapperInterface, methodCache,最终是通过JDK的动态代理创建返回代理对象(类型是$Proxy数字)。这个对象继承Proxy类,实例被代理的接口,里面持有了一个MapperProxy类型的触发管理类。

3.2 MapperProxy 实现对接口的代理

JDK的动态代理有三个核心角色:

  • 被代理类(实现类)
  • 接口
  • 实现了InvocationHandler的触发管理类

用来生成代理对象。被代理的类必须实现接口,因为要通过接口获取方法,而且代理类也要实现这个接口。 技术图片 而MyBatis里面的Mapper没有实现类,它直接忽略了实现类,直接对接口进行代理。

技术图片 获取Mapper对象的过程,实际上是获取了一个JDK动态代理对象。这个代理类继承Proxy类,实现被代理的接口,里面持有一个MapperProxy类型的触发管理类。来看下代理类过程的时序图吧。

技术图片

四、执行SQL

Business business = mapper.selectBusinessById(1);

4.1 MapperProxy.invoke()方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,无需走到执行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
        // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

4.1.1、首先判断是否要执行SQL还是直接执行方法

Object本身的方法和Java 8中的默认方法不需要取执行SQL

4.1.2、获取缓存

这里加入缓存时为了提升MapperMethod的获取速度

 private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // Java8 中 Map 的方法,根据 key 获取值,如果值是 null,则把后面Object 的值赋给 key
      // 如果获取不到,就创建
      // 获取的是 MapperMethodInvoker(接口) 对象,只有一个invoke方法
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          // 接口的默认方法(Java8),只要实现接口都会继承接口的默认方法,例如 List.sort()
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          // 创建了一个 MapperMethod
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

Map 的 computeIfAbsent()方法:只有key不存在或者value为null,则把后面的Object的值赋给key。Java8和Java9中的接口默认方法由特殊处理,返回DefaultMethodInvoker对象。普通的方法返回的是PlainMethodInvoker,MapperMethod。

在MapperMethod对象中有两个比较重要的属性:

 // statement id (例如:com.sy.mapper.BusinessMapper.selectBusinessById) 和 SQL 类型
  private final SqlCommand command;
  // 方法签名,主要是返回值的类型
  private final MethodSignature method;

4.2 MappperMethod.execute()方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      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);
          // 普通 select 语句的执行入口 >>
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

根据不同的类型(INSERT、UPDATE、DELETE、SELECT)和返回类型:

  • 调用convertArgsToSqlCommandParam()将方法的参数转换为SQL的参数。
  • 调用sqlSession的insert()、update()、delete()、selectOne()方法。

下面重点来讲下查询的selectOne()方法。调用了DefaultSqlSession的selectOne()方法。

4.3 DefaultSqlSession.selectOne()方法

public <T> T selectOne(String statement, Object parameter) {
    // 来到了 DefaultSqlSession
    // 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;
    }
  }

在SelectList()中,我们先根据commandname(StatementID)从Configuration中拿到MappedStatement,这个ms上面有我们在xml中配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
      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.query(),前面我们说到了Executor有三种基本类型,SIMPLE/REUSE/BATCH,还有一种包装类型,CachingExecutor。那么在这里到底会选择哪一种执行器呢?我们要回过头去看看DefaultSqlSession在初始化的时候是怎么赋值的,这个就是我们的会话创建过程。如果启用了二级缓存,就会先调用CachingExecutor 的query()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的SimpleExecutor。在没有开启二级缓存的情况下,先会走到BaseExecutor的query()方法(否则会先走到CachingExecutor)。

4.4 CachingExector.query()方法

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建CacheKey:什么样的SQL是同一条SQL? >>
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

4.4.1、创建CacheKey

二级缓存的CacheKey是如何构成的呢?换句话说,什么样的查询才能确定是同一个查询呢?在BaseExector中createCacheKey()方法,用到了六要素:

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId()); 
    cacheKey.update(rowBounds.getOffset()); // 0
    cacheKey.update(rowBounds.getLimit()); // 2147483647 = 2^31-1
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value); // development
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

即为方法相同、翻页偏移相同、SQL相同、参数值相同、数据源环境相同才会被认定为同一个查询。

注意看下CacheKey类的属性,里面有一个List按照顺序存放了上面的六要素。

 private static final int DEFAULT_MULTIPLIER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  private final int multiplier;
  private int hashcode;
  private long checksum;
  private int count;
  // 8/21/2017 - Sonarlint flags this as needing to be marked transient.  While true if content is not serializable, this is not always true and thus should not be marked transient.
  private List<Object> updateList;

怎么比较两个CacheKey是否相等呢?如果一上来就依次比较六个要素是否相等,这样要比6次,这样效率不高。每一个类都继承自Object,都有一个hashCode()方法,用来生成哈希码。它是用来在集合中快速判重的。

在生成cacheKey的时候也就是调用update()方法,也更新了cacheKey的hashCode,它是用乘法哈希生成的。

public void update(Object object) {
    // 加法哈希
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    // 37 * 17 + 
    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
  }

Object中的hashCode()方法是一个本地方法,通过随机数算法生成(OpenJDK8 默认,可以通过-XX:hashCode修改)。CacheKey中的hashCode()方法进行了重写,返回生成新的hashCode。

为什么需要用37作为乘法因子呢?这是一个经验值,跟String类中的31类似。看下String类中的hashCode()方法源码:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            //31作为乘法因子
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

CacheKey中的equals()方法也进行了重写,比较cacheKey是否相等。

@Override
public boolean equals(Object object) {
    // 同一个对象
    if (this == object) {
      return true;
    }
    // 被比较的对象不是 CacheKey
    if (!(object instanceof CacheKey)) {
      return false;
    }
    
    final CacheKey cacheKey = (CacheKey) object;
    
    // hashcode 不相等
    if (hashcode != cacheKey.hashcode) {
      return false;
    }
    // checksum 不相等
    if (checksum != cacheKey.checksum) {
      return false;
    }
    // count 不相等
    if (count != cacheKey.count) {
      return false;
    }
    
    for (int i = 0; i < updateList.size(); i++) {
      Object thisObject = updateList.get(i);
      Object thatObject = cacheKey.updateList.get(i);
      if (!ArrayUtil.equals(thisObject, thatObject)) {
        return false;
      }
    }
    return true;
}

如果哈希值(乘法哈希),校验值(加法哈希),要素个数任何一个不相等,都不是同一个查询,最后再循环比较要素,防止hash碰撞。

CacheKey生成之后,调用CachingExecutor类的query()方法。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建CacheKey:什么样的SQL是同一条SQL? >>
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

4.4.2、处理二级缓存

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    // cache 对象是在哪里创建的?  XMLMapperBuilder类 xmlconfigurationElement()
    // 由 <cache> 标签决定
    if (cache != null) {
      // flushCache="true" 清空一级二级缓存 >>
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // 获取二级缓存
        // 缓存通过 TransactionalCacheManager、TransactionalCache 管理
        @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;
      }
    }
    // 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

首先从MappedStatement对象中调用getCache()方法,判断对象是否为空,如果为空,则没有查询二级缓存、写入二级缓存的流程。

那么Cache对象是什么时候被创建出来的呢?用来解析Mapper.xml的XMLMapperBuilder类,configurationElement()方法中调用cacheElement()方法:

cacheElement(context.evalNode("cache"));

只有Mapper.xml中的cache标签不为空才会被解析。

 private void cacheElement(XNode context) {
    // 只有 cache 标签不为空才解析
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

builderAssistant.useNewCache()方法创建了一个Cache对象。

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

二级缓存为什么要用TCM来进行管理呢?

我们来思考一个问题,在一个事务中:

  • 1、首先插入一条数据(没有提交),此时二级缓存会被清空。
  • 2、在这个事务中查询数据,写入二级缓存。
  • 3、提交事务,出现异常,数据回滚。

此时出现了数据库没有这条数据,但是二级缓存有这条数据的情况。所以MyBatis 的二级缓存需要跟事务关联起来。

那么为什么一级缓存不这么做?

因为一个session就是一个事务,事务回滚,会话就结束了,缓存也清空了,不存 在读到一级缓存中脏数据的情况。二级缓存是跨session的,也就是跨事务的,才有可能出现对同一个方法的不同事务访问。

4.4.2.1 写入二级缓存

tcm.putObject(cache, key, list); // issue #578 and #116

调用TranscationalCacheManager的putObject()方法,从map中拿出TransactionalCache对象,把value添加到待提交的map中。此时缓存还没有真正的写入。

public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
}

private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}

调用TranscationalCache的putObject()方法

@Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

只有真正的提交事务的时候才真正的写入缓存。

4.4.2.2 获取二级缓存

List<E> list = (List<E>) tcm.getObject(cache, key);

从map中拿出Transcational对象,这个对象也是对PerpetualCache经过层层装饰的缓存对象。getObject()方法层层递归,直到到达PerpetualCache,拿到value。

@Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

PerpetualCache中的getObject()方法:

@Override
public Object getObject(Object key) {
    return cache.get(key);
}

4.5 BaseExecutor.query()方法

4.5.1 清空本地缓存

queryStack用于记录查询栈,防止递归时候查询重复处理缓存。flushCache=true的时候,会先清除本地缓存LocalCache(一级缓存)。

if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // flushCache="true"时,即使是查询,也清空一级缓存
      clearLocalCache();
}

如果没有缓存,会从数据库查询。调用**queryFromDatabase()**方法。

list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

LocalCacheScope == STATEMENT,就会清空本地缓存。

if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    // issue #482
    clearLocalCache();
}

4.5.2 数据库查询

1、先在缓存用占位符进行占位。执行查询后,移除占位符,放入数据。

2、执行Exector的doQuery()方法,默认是SimpleExector。

 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 {
      // 三种 Executor 的区别,看doUpdate
      // 默认Simple
      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;
  }

4.6 SimpleExecutor.query()方法

4.6.1 创建StatementHandler

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 注意,已经来到SQL处理的关键对象 StatementHandler >>
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 获取一个 Statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 执行查询
      return handler.query(stmt, resultHandler);
    } finally {
      // 用完就关闭
      closeStatement(stmt);
    }
  }

在configuration.newStatementHandler()先得到RoutingStatementHandler。RoutingStatementHandler没有任何实现,用来创建基本的StatementHandler,这里会根据MappedStatement里面的statementType决定StatementHandler的类型。默认是PREPARED(STATEMENT、PREPARED、CALLABLE)。

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // StatementType 是怎么来的? 增删改查标签中的 statementType="PREPARED",默认值 PREPARED
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        // 创建 StatementHandler 的时候做了什么? >>
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

StatementHandler里面包含了处理参数的ParamterHandler和处理结果集的ResultHandler。这两个对象都是在上面new的时候创建的。

StatementHandler父类BaseStatementHandler类中的构造函数中创建以上两个对象。

 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    // 创建了四大对象的其它两大对象 >>
    // 创建这两大对象的时候分别做了什么?
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

这些对象都是可以被插件拦截的四大对象,所以在创建之后都要用拦截器进行包装的方法。在Configuration中进行拦截调用。

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 植入插件逻辑(返回代理对象)
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    // 植入插件逻辑(返回代理对象)
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 植入插件逻辑(返回代理对象)
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

这里只有对其中的三个对象,还有一个对象呢?它什么时候创建呢?

4.6.2 创建Statement

用new出来的StatementHandler创建Statement对象。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    // 获取 Statement 对象,如果有插件包装,会先走到被拦截的业务逻辑
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 为 Statement 设置参数,对sql语句进行预编译,处理参数
    handler.parameterize(stmt);
    return stmt;
}
@Override
public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
}

4.6.3 执行StatementHandler的query()方法

RoutingStatementHandler的query()方法,delegate委派,最终执行PreparedStatementHandler的query()方法。

 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

4.6.4 执行PreparedStatementHandler的query()方法

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 到了JDBC的流程
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

4.6.5 ResultHandler处理结果集

resultSetHandler.handleResultSets(ps);

怎么把ResultSet转换成List<ObJect>?

ResultSetHandIer只有一个实现类:DefaultResultSetHandler也就是执行 DefaultResultSetHandler的handleResultSets()方法。首先我们会先拿到第一个结果集,如果没有配置一个查询返回多个结果集的情况,一般只有一个结果集。如果下面的这个while循环我们也不用,就执行一次。然后会调用handleResuItSet()方法。

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

技术图片来源:站长

以上是关于手把手教你阅读mybatis核心源码,掌握底层工作原理与设计思想的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你写!dockervolumes容器

手把手教你基于SpringBoot+MyBatis实现员工管理系统‍附完整源码

手把手教你基于SpringBoot+MyBatis实现员工管理系统‍附完整源码

Netty进阶:手把手教你如何编写一个NIO服务端

❤爆肝万字手把手教你SpringBoot+MyBatis+jQuery+HTML5从0开始写网页一学就会!(内附源码)❤

手把手带你阅读Mybatis源码缓存篇