/ Java / 76浏览

Mybatis源码快速阅读

创建SqlSessionFactory

先读取xml配置文件,解析得到Configuration,然后使用DefaultSqlSessionFactory创建

  # 读取xml配置文件
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
  }
  # 创建 SqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

Configuration.parse

保证配置文件只会解析一次

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

解析配置文件

重点看mapperElement(root.evalNode("mappers")),解析mapper

private void parseConfiguration(XNode root) {
  try {
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);

    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

mappers

mappers节点可以写一个package或者一个mapper,其中mapper可以写class、url、resource中的一个。
package 会扫描包下所有class依次调用configuration.addMapper

<mappers>
  
  <!-- 使用相对于类路径的资源引用 -->
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  
  <!-- 使用完全限定资源定位符(URL) -->
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  
  <!-- 使用映射器接口实现类的完全限定类名 -->
  <mapper class="org.mybatis.builder.BlogMapper"/>
  
  <!-- 将包内的映射器接口实现全部注册为映射器 -->
  <package name="org.mybatis.builder"/>

</mappers>

mapper

可以看出解析优先级是 resource > url > class
url和resource 最后都会调用XMLMapperBuilder.parse
class 则会直接调用configuration.addMapper

String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
  ErrorContext.instance().resource(resource);
  try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    mapperParser.parse();
  }
} else if (resource == null && url != null && mapperClass == null) {
  ErrorContext.instance().resource(url);
  try(InputStream inputStream = Resources.getUrlAsStream(url)){
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
    mapperParser.parse();
  }
} else if (resource == null && url == null && mapperClass != null) {
  Class<?> mapperInterface = Resources.classForName(mapperClass);
  configuration.addMapper(mapperInterface);
} else {
  throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}

XMLMapperBuilder.parse

解析mapper前会检查当前resource是否解析过,isResourceLoaded函数是通过判断resource是否在一个set集合内,不在的话会解析该resource,然后addLoadedResource将resource加入集合。
(上面传入的url、resource)
解析成功后会调用bindMapperForNamespace()

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

configurationElement解析mapper.xml文件

mapper.xml中的namespace代表对应的接口类,必须存在。
该函数会依次解析一下节点

  • cache-ref (引用其它命名空间的缓存配置)
  • cache (缓存配置)
  • /mapper/parameterMap (老式风格的参数映射、已被废弃)
  • /mapper/resultMap (结果映射)
  • /mapper/sql (可被其它语句引用的可重用语句块)
  • select|insert|update|delete
    重点看 buildStatementFromContext(context.evalNodes(“select|insert|update|delete”))
private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

select|insert|update|delete

buildStatementFromContext方法会调用buildStatementFromContext。
循环遍历所有节点,每个节点都会创建一个XMLStatementBuilder statementParser 对象,然后调用其parseStatementNode方法。
解析失败时会将statementParser添加到incompleteStatements。
后续所有节点解析完后回再调用parsePendingStatements()再次解析incompleteStatements集合内失败的节点。

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

parseStatementNode

源码很长,简单来说就是解析得到对应的属性,然后传入builderAssistant.addMappedStatement。
注意一下这个sqlSource,后续都是通过这个获取执行的sql。
createSqlSource会调用XMLLanguageDriver.createSqlSource,
然后调用XMLScriptBuilder.parseScriptNode 来获取SqlSource,获取流程

  1. 使用XMLScriptBuilder.parseDynamicTags包装sql得到MixedSqlNode rootSqlNode
  2. rootSqlNode被包装到 DynamicSqlSource 或者 RawSqlSource
  3. DynamicSqlSource 用于解析${} ,使用字符串拼接,DynamicSqlSource 初始化时只会做赋值操作,调用getBoundSql才会解析sql
  4. RawSqlSource 用于解析不包含${} ,使用 ? 替换 #{},RawSqlSource 初始化时就会调用其getSql解析sql
public void parseStatementNode() {
  // ....
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

  // ...
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

# isDynamic 代表xml中的sql是否包含 ${} 
public SqlSource parseScriptNode() {
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

builderAssistant.addMappedStatement

这里会使用MappedStatement.Builder创建一个MappedStatement statement,然后传入configuration.addMappedStatement

public MappedStatement addMappedStatement( ... ) {

  id = applyCurrentNamespace(id, false);
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  MappedStatement.Builder statementBuilder = new MappedStatement.Builder();
  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  if (statementParameterMap != null) {
    statementBuilder.parameterMap(statementParameterMap);
  }
  MappedStatement statement = statementBuilder.build();
  configuration.addMappedStatement(statement);
  return statement;
}

addMappedStatement

这里只是简单的将statement放入一个Map,这里的Map是Mybatis自定义的一个StrictMap
protected static class StrictMap<V> extends HashMap<String, V>

public void addMappedStatement(MappedStatement ms) {
  mappedStatements.put(ms.getId(), ms);
}

bindMapperForNamespace

这个方法是通过mapper.xml的namespace获取接口Class,然后调用configuration.addMapper
Mybatis并不强制要求使用接口,所以namespace不存在是不会抛出异常。

private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      // ignore, bound type is not required
    }
    if (boundType != null && !configuration.hasMapper(boundType)) {
      configuration.addLoadedResource("namespace:" + namespace);
      configuration.addMapper(boundType);
    }
  }
}

configuration.addMapper

这是只是简单调用mapperRegistry.addMapper

public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
}

mapperRegistry.addMapper

  1. 根据type创建代理工厂new MapperProxyFactory<>(type))并加入名为knownMappers的Map中。
public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // It's important that the type is added before the parser is run
      // otherwise the binding may automatically be attempted by the
      // mapper parser. If the type is already known, it won't try.
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

MapperProxyFactory

传入MapperProxy使用JDK动态代理生成代理类,也就是这里让Mybatis只用写接口就可以调用。
传入sqlSession也会被包装成MapperProxy

public class MapperProxyFactory<T> {
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }
  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    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,用于JDK动态代理调用,生成的代理类实际上也是调用的SqlSession内的selectOne、selectList等方法。

public class MapperProxy<T> implements InvocationHandler, Serializable {

}

SqlSession

public interface SqlSession extends Closeable {
  <T> T selectOne(String statement);
  <E> List<E> selectList(String statement);
  // ...
  <T> T getMapper(Class<T> type);
}

SqlSession.getMapper

调用configuration.getMapper(type, sqlSession) -> mapperRegistry.getMapper(type, sqlSession)

getMapper

从knownMappers容器中获取mapper代理工厂,调用mapperProxyFactory.newInstance(sqlSession)获取代理类。

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

SqlSession.selectList

  1. 根据 statement(String) 从 configuration.getMappedStatement 获取 MappedStatement ms
  2. 使用 executor 执行 ms
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

BaseExecutor.query (一级缓存)

  1. getBoundSql获取sql
  2. 调用queryFromDatabase
  3. 调用子类doQuery
  4. 查询结果会放入缓存localCache,这里就是Mybatiss的一级缓存
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameter);
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> query( ... ){
  List<E> list;
  try {
    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);
    }
  } 
}

private <E> List<E> queryFromDatabase( ... )
  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;
}

doQuery

  1. SimpleExecutor 查询完成会关闭Statement
  2. ReuseExecutor 不会关闭Statement,会将创建的Statement放到一个Map<String, Statement> statementMap,key就是执行的sql
// SimpleExecutor.doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

// ReuseExecutor.doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  Statement stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.query(stmt, resultHandler);
}

二级缓存

如果开启了二级缓存时,executor会采用CachingExecutor。

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 = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

二级缓存查询流程

得到cachekey后
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql)
会先获取二级缓存,获取不到再执行上面一级缓存流程

public <E> List<E> query( ... )
    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);
}
@Override