上一篇我们介绍了Configuration类的成员变量,这一篇我们通过XMLMapperBuilder&XMLMapperBuilder类来看下对mybatis xml配置文件的解析。

一、XMLConfigBuilder基本介绍

1、基本结构&成员变量

public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  private XPathParser parser;
  private String environment;

​ 可以看到其是有继承BaseBuilder的,然后:parsed参数是用来表明这个已经解析过了、parser就是包含解析的数据流,以及解析方法

1)、XPathParser

public class XPathParser {

  private Document document;
  private boolean validation;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;

​ 这个Document就是将mybatis的初始化xml的输入流变为一个Document对象。

二、XMLConfigBuilder的解析方法

1、parse()

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

这个parser.evalNode("/configuration")就是获取configuration节点。

<configuration>
  <settings>
    <setting name="lazyLoadingEnabled" value="true"/>
  </settings>

  <typeAliases>
    <typeAlias alias="Person" type="org.apache.ibatis.submitted.cglib_lazy_error.Person"/>
  </typeAliases>

  <environments default="test">
    <environment id="test">
      <transactionManager type="JDBC"></transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:mem:cglib_lazy_error"/>
        <property name="username" value="sa"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="org/apache/ibatis/submitted/cglib_lazy_error/Person.xml"/>
  </mappers>
</configuration>

2、parseConfiguration(XNode root)

private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    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);
    // read it after objectFactory and objectWrapperFactory issue #631
    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);
  }
}

这个方法就是对对应节点的具体分析了。

3、propertiesElement() [properties]节点

<properties resource="org/apache/ibatis/submitted/nested_query_cache/mapper.properties">
  <property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
</properties>
private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    Properties defaults = context.getChildrenAsProperties();
    String resource = context.getStringAttribute("resource");
    String url = context.getStringAttribute("url");
    if (resource != null && url != null) {
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    }
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
    parser.setVariables(defaults);
    configuration.setVariables(defaults);
  }
}
public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {
        properties.setProperty(name, value);
      }
    }
    return properties;
}

​ 这个方法就是先通过getChildrenAsProperties方法将properties节点的内容转换为Properties。

​ 然后获取该节点的resource、url属性的值,可以看到然后有一个检查表明此节点只能使用一个属性。然后再会通过对应的方法将这些要加载的内容再转换为properties添加到defaults中,再将properties添加到configuration中。

4、settingsAsProperties [settings]

private Properties settingsAsProperties(XNode context) {
  if (context == null) {
    return new Properties();
  }
  Properties props = context.getChildrenAsProperties();
  // Check that all settings are known to the configuration class
  MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  for (Object key : props.keySet()) {
    if (!metaConfig.hasSetter(String.valueOf(key))) {
      throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
    }
  }
  return props;
}

​ 这个方法也是将该节点转换为对应的Properties对象表示。但这里有使用一个新的类MetaClass,这个类可是对其的入参Configuration.class这个类的反射内容表示,其有一个localReflectorFactory 反射工厂参数,到时候有时间的话可以用番外篇来具体梳理下。这里的hasSetter方法就是用来看Configuration这个类有没有这个key对应的属性的set方法。我们在梳理Configuration类的成员变量有将,Configuration中的一些整体设置属性就是读取配置文件中的,这里等于是对settings节点中配置的内容的校验,可能你写错写了一些mybatis不能识别的节点属性内容。

5、loadCustomVfs&loadCustomLogImpl

private void loadCustomVfs(Properties props) throws ClassNotFoundException {
  String value = props.getProperty("vfsImpl");
  if (value != null) {
    String[] clazzes = value.split(",");
    for (String clazz : clazzes) {
      if (!clazz.isEmpty()) {
        @SuppressWarnings("unchecked")
        Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
        configuration.setVfsImpl(vfsImpl);
      }
    }
  }
}
private void loadCustomLogImpl(Properties props) {
  Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);
}

这两个就是获取对应在settings中配置的log实现&vfs实现(vfs现在还不是很了解,其是一种数据格式好像)。

6、typeAliasesElement [typeAliases]节点

<typeAliases>
  <package name="org.apache.ibatis.submitted.nestedresulthandler_gh1551"/>
</typeAliases>
<typeAliases>
  <typeAlias alias="Person" type="org.apache.ibatis.submitted.map_class_name_conflict.Person"/>
</typeAliases>
private void typeAliasesElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String typeAliasPackage = child.getStringAttribute("name");
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {
        String alias = child.getStringAttribute("alias");
        String type = child.getStringAttribute("type");
        try {
          Class<?> clazz = Resources.classForName(type);
          if (alias == null) {
            typeAliasRegistry.registerAlias(clazz);
          } else {
            typeAliasRegistry.registerAlias(alias, clazz);
          }
        } catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}

这个就是别名注册可以看到其有两种,一种就是写包名,其会将包名下的类都注册到这里。还有一种就是写别名及对应的类,然后根据type写的内容将其对应的类获取再进行注册。

public void registerAlias(String alias, Class<?> value) {
  if (alias == null) {
    throw new TypeException("The parameter alias cannot be null");
  }
  // issue #748
  String key = alias.toLowerCase(Locale.ENGLISH);
  if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
    throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
  }
  typeAliases.put(key, value);
}

​ 可以看到这里是会将alias变为小写的key。

在这里插入图片描述

7、pluginElement [plugins]节点

<plugins>
  <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
    <property name="pluginProperty" value="100"/>
  </plugin>
</plugins>
<plugins>
  <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
    <property name="pluginProperty" value="100"/>
  </plugin>
</plugins>
public class ExamplePlugin implements Interceptor {
  private Properties properties;
private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      String interceptor = child.getStringAttribute("interceptor");
      Properties properties = child.getChildrenAsProperties();
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

这个就是用来解析获取添加自定义mybatis的Interceptor接口的实现类的,可以看到其会添加的configuration中,同时通过这个案例及对应代码可以看到其会将写的[property]节点的内容设置到这里创建的interceptorInstance中。

8、objectFactoryElement[objectFactory]节点

private void objectFactoryElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties properties = context.getChildrenAsProperties();
    ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(properties);
    configuration.setObjectFactory(factory);
  }
}

这里就是获取objectFactory节点的内容去创建一个对应的对象工厂,然后设置到configuration中,可以看到这里应该也能添加[property]节点。

9、objectWrapperFactoryElement [objectWrapperFactory]节点

private void objectWrapperFactoryElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    configuration.setObjectWrapperFactory(factory);
  }
}

​ 对象包装对象创建,同时设置到configuration中,这里是没有[property]节点对应的赋值的。

10、reflectorFactoryElement [reflectorFactoryElement]节点

​ 这个同[objectWrapperFactory]节点节点的内容。

11、settingsElement(settings)

private void settingsElement(Properties props) {
  configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
  configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
  configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
  configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
  configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
  configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
  configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
  configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
   ......
  configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
}

这个方法就是将前面解析的[settings]节点的内容都赋值到configuration中的变量中。

12、environmentsElement [environments]

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}
private boolean isSpecifiedEnvironment(String id) {
  if (environment == null) {
    throw new BuilderException("No environment specified.");
  } else if (id == null) {
    throw new BuilderException("Environment requires an id attribute.");
  } else if (environment.equals(id)) {
    return true;
  }
  return false;
}

这个就是用来解析创建对应的Environment的。

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}

可以看到这里主要有三个信息,一个是Environment的id(String id = child.getStringAttribute(“id”)),然后再根据对应的信息创建事务工厂&数据源工厂,这里都是将[property]节点的内容转换为了Properties。

13、databaseIdProviderElement [databaseIdProvider]

这个是与databaseId相关的内容,我们在前一篇Configuration中有简答提到,但还没有使用过,就不具体分析了。

14、typeHandlerElement [typeHandlers]节点

<typeHandlers>
  <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
</typeHandlers>
public class CustomStringTypeHandler implements TypeHandler<String> {
 ......
}

​ 这个就是用来注册你自己的类型转换器的。

private void typeHandlerElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        typeHandlerRegistry.register(typeHandlerPackage);
      } else {
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
        String handlerTypeName = child.getStringAttribute("handler");
        Class<?> javaTypeClass = resolveClass(javaTypeName);
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        Class<?> typeHandlerClass = resolveClass(handlerTypeName);
        if (javaTypeClass != null) {
          if (jdbcType == null) {
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}

​ 可以看到这里还可以直接扫描整个包下的TypeHandler。

15、mapperElement [mappers]节点

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        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);
          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);
          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.");
        }
      }
    }
  }
}

​ 这里我们先简单分析下其的逻辑,遍历其子节点

1)、这里首先是判断是不是以package的形式

<mappers>
    <package name="org.apache.ibatis.submitted.autodiscover.mappers" />
</mappers>

这种类型是调用configuration.addMappers(mapperPackage)方法。

2)、剩下的是[mapper]节点

可以看到这种节点有3种属性:类路径、类名、绝对路径

<mapper resource="org/apache/ibatis/submitted/cglib_lazy_error/Person.xml"/>
<mapper class="org.apache.ibatis.submitted.array_result_type.Mapper" />
<mapper url="file:./src/test/java/org/apache/ibatis/builder/NestedBlogMapper.xml"/>

可以看到resource&url这种由于是要读取xml文件的,梳理会将这两种加载形成InputStream在通过XMLMapperBuilder去解析,class能直接创建类,则调用configuration.addMapper(mapperInterface)方法。

三、<mapper>节点解析的一些类&方法

前面我们梳理了对于<mapper>节点解析的整体逻辑,现在我们来分析其节点解析相关的具体方法

1、addMappers(String packageName) Configuration

public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
}
protected MapperRegistry mapperRegistry = new MapperRegistry(this);

​ 可以看到这个方法是再调用mapperRegistry的addMappers方法。

1)、addMappers(String packageName) MapperRegistry

public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

​ 这里最终是通过ResolverUtil类来获取在该包下的所有类,再遍历进入到addMapper(mapperClass)。

2、addMapper(Class<T> type)

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<T>(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);
        }
      }
    }
  }
public class MapperRegistry {

  private Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

​ 可以看到这里是先判断是不是接口,再通过hasMapper判断这个类有没有被注册添加到knownMappers,没有再添加到knownMappers中,同时通过Class&Configuration去构建MapperAnnotationBuilder。

public class MapperAnnotationBuilder {

  private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
  private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();
  private Configuration configuration;
  private MapperBuilderAssistant assistant;
  private Class<?> type;
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
  String resource = type.getName().replace('.', '/') + ".java (best guess)";
  this.assistant = new MapperBuilderAssistant(configuration, resource);
  this.configuration = configuration;
  this.type = type;

  sqlAnnotationTypes.add(Select.class);
  sqlAnnotationTypes.add(Insert.class);
  sqlAnnotationTypes.add(Update.class);
  sqlAnnotationTypes.add(Delete.class);

  sqlProviderAnnotationTypes.add(SelectProvider.class);
  sqlProviderAnnotationTypes.add(InsertProvider.class);
  sqlProviderAnnotationTypes.add(UpdateProvider.class);
  sqlProviderAnnotationTypes.add(DeleteProvider.class);
}

​ resource就是这个类对应的java文件所在的位置,然后这里的两种注解sqlAnnotationTypes&sqlProviderAnnotationTypes我们再上一篇有梳理,这里就不再赘叙了,不过下面还会提到,这个时候将这两个list添加对应的注解。之后再调用MapperAnnotationBuilder的parse()方法进行对应的解析。

1)、parse() MapperAnnotationBuilder

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

​ 这里先是判断这个resource是否已经加载了。然后是通过loadXmlResource()方法去加载这个Mapper接口所对应的xml文件,去解析这个xml文件的信息。之后再通过parseStatement(method)方法去处理这个Class的方法。

2)、loadXmlResource() MapperAnnotationBuilder

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

这个方法就是去加载这个Mapper接口对应的xml文件,将去转换为流去构建XMLMapperBuilder,再调用parse()方法去解析,这个方法我们到下面再梳理。需要注意的是,可以不需要这个xml文件 (// ignore, resource is not required)。

3)、parseStatement(Method method) MapperAnnotationBuilder

void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      ......
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;
      ......
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        ......
        }
      } else {
        keyGenerator = new NoKeyGenerator();
      }

      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      ......
      assistant.addMappedStatement(mappedStatementId,......);
    }
  }
public MappedStatement addMappedStatement(
      String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {
       .......
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }
public void addMappedStatement(MappedStatement ms) {  mappedStatements.put(ms.getId(), ms);}

​ 这个parseStatement(Method method)就是来解析Mpper接口中的方法的,再将围绕这个接口的的一些注解信息如@SelectKey、ResultMap、@Select、@Options等,及方法本身的信息去构建对应的MappedStatement,主要处理注解注入的sql相关的内容。

​ 下面我们来具体分析下parseStatement(Method method)方法中的一些调用方法。

4)、getSqlSourceFromAnnotations

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      if (sqlAnnotationType != null) {
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
      }
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }
 private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
    final StringBuilder sql = new StringBuilder();
    for (String fragment : strings) {
      sql.append(fragment);
      sql.append(" ");
    }
    return languageDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);
  }
private Class<? extends Annotation> getSqlAnnotationType(Method method) {
    return chooseAnnotationType(method, sqlAnnotationTypes);
  }

  private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
    return chooseAnnotationType(method, sqlProviderAnnotationTypes);
}
private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
    for (Class<? extends Annotation> type : types) {
      Annotation annotation = method.getAnnotation(type);
      if (annotation != null) {
        return type;
      }
    }
    return null;
  }

​ 可以看到这里是构建获取SqlSource(ProviderSqlSource),我们上一篇有提到有两种:sqlAnnotationTypes(@Select…) | sqlProviderAnnotationType(@SelectProvider) (在上面MapperAnnotationBuilder构建的时候添加到这两个list中),在这里也会判断不能两种注解都有,不然会报BindingException异常。

​ 如果是@Select这种,可以看到其是通过buildSqlSourceFromStrings方法调用的languageDriver.createSqlSource去选择创建对应的SqlSource(RawSqlSource|DynamicSqlSource)。如果是@SelectProvider这种就直接创建ProviderSqlSource对象。

5)、getSqlCommandType

private SqlCommandType getSqlCommandType(Method method) {
    Class<? extends Annotation> type = getSqlAnnotationType(method);

    if (type == null) {
      type = getSqlProviderAnnotationType(method);

      if (type == null) {
        return SqlCommandType.UNKNOWN;
      }

      if (type == SelectProvider.class) {
        type = Select.class;
      } else if (type == InsertProvider.class) {
        type = Insert.class;
      } else if (type == UpdateProvider.class) {
        type = Update.class;
      } else if (type == DeleteProvider.class) {
        type = Delete.class;
      }
    }
    return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
  }

​ 这个方法是看当前是那种数据库操作创建对应的SqlCommandType。

2、parse() XMLMapperBuilder

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

​ 现在我们来看下在前面提loadXmlResource() 时跳过的方法。

1)、configurationElement(XNode context)

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

​ 这个方法就是解析整个<mapper>节点的内容。首先是获取命名空间的内容并进行为空校验。

2)、parameterMapElement(List<XNode> list)

private void parameterMapElement(List<XNode> list) {
  for (XNode parameterMapNode : list) {
    String id = parameterMapNode.getStringAttribute("id");
    String type = parameterMapNode.getStringAttribute("type");
    Class<?> parameterClass = resolveClass(type);
    List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
    List<ParameterMapping> parameterMappings = new ArrayList<>();
    for (XNode parameterNode : parameterNodes) {
      String property = parameterNode.getStringAttribute("property");
      String javaType = parameterNode.getStringAttribute("javaType");
      String jdbcType = parameterNode.getStringAttribute("jdbcType");
      String resultMap = parameterNode.getStringAttribute("resultMap");
      String mode = parameterNode.getStringAttribute("mode");
      String typeHandler = parameterNode.getStringAttribute("typeHandler");
      ......
      Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
      ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
      parameterMappings.add(parameterMapping);
    }
    builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
  }
}
public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
  id = applyCurrentNamespace(id, false);
  ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
  configuration.addParameterMap(parameterMap);
  return parameterMap;
}

​ 这里是提取参数映射,这个方法就是遍历提取所有的<parameterMap>节点,获取其的节点信息,将其添加到Configuration中的parameterMaps。

3)、resultMapElements(List<XNode> list)

​ 这个与前面的parameterMapElement方法类似,提取返回属性的映射,设置到Configuration的resultMaps中。

4)、sqlElement(List<XNode> list)

private void sqlElement(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    sqlElement(list, configuration.getDatabaseId());
  }
  sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    String databaseId = context.getStringAttribute("databaseId");
    String id = context.getStringAttribute("id");
    id = builderAssistant.applyCurrentNamespace(id, false);
    if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
      sqlFragments.put(id, context);
    }
  }
}

​ 这里就是提取sql片段(看其看到其与dataSourceId有关联),这个sqlFragments遍历也是指向Configuration的,在其通过构造函数创建的时候传入。

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  super(configuration);
  this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
  this.parser = parser;
  this.sqlFragments = sqlFragments;
  this.resource = resource;
}

5)、buildStatementFromContext(List<XNode> list)

private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}
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);
    }
  }
}

​ 这里最后是通过创建XMLStatementBuilder对象去解析。

6)、parseStatementNode() XMLStatementBuilder

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }
  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  ......
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

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

​ 这里其实与前面的MapperAnnotationBuilder类似,只是那里是解析Mapper接口中方法去构建MappedStatement,这里是直接读取节点中的内容去解析创建MappedStatement。

在这里插入图片描述

在这里插入图片描述

public class MapperBuilderAssistant extends BaseBuilder {

  private String currentNamespace;
  private final String resource;
  private Cache currentCache;
  private boolean unresolvedCacheRef; // issue #676

  public MapperBuilderAssistant(Configuration configuration, String resource) {
    super(configuration);
    ErrorContext.instance().resource(resource);
    this.resource = resource;
  }

​ 至此,我们就梳理了Mybatis对于Mapper接口的注解提取对应内容创建MappedStatement以及提取xml文件提取对应节点的内容创建

MappedStatement的过程,以及将这些内容设置到Configuration中。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐