【MyBatis】mapper.xml解析及annotation支持源码分析

项目搭建

参考之前的一篇文章【MyBatis】基本使用

XML解析过程分析

解析入口

  • SqlSessionFactoryBean
    【MyBatis】mapper.xml解析及annotation支持源码分析_第1张图片
    SqlSessionFactoryBean.png

    由于SqlSessionFactoryBean实现了InitializingBean接口,所以Spring加载完SqlSessionFactoryBean实例后会调用afterPropertiesSet方法
    org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

在xml中配置给SqlSessionFactoryBean初始化时会自动将配置拷贝到Configuration中,Configuration其实就是MyBatis的一个配置中心。

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      //解析全局配置文件  
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }

   ......

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

下面详细解释一下其中的几个类

  • Configuration
    MyBatis配置的一个容器,注册了一些默认的属性,以及后面生成的ResultMap、MappedStatement等都会加入这个容器中
package org.apache.ibatis.session;
public class Configuration {

 protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map mappedStatements = new StrictMap("Mapped Statements collection");
  protected final Map caches = new StrictMap("Caches collection");
  protected final Map resultMaps = new StrictMap("Result Maps collection");
  protected final Map parameterMaps = new StrictMap("Parameter Maps collection");
  protected final Map keyGenerators = new StrictMap("Key Generators collection");
......

public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

  ......
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }
}
  • ConfigLocation

是指全局配置文件的路径,因为这些配置都可以直接在SqlSessionFactoryBean 中配置所以现在一般都不再直接新增一个Mybatis的配置文件

 Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }
  • Environment
 if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
  • mapperLocations
    mapper文件的路径

开始解析mapper.xml

SqlSessionFactoryBean#buildSqlSessionFactory中遍历mapperLocations,并一个一个的解析mapper文件。

#org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
#org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //解析mapper.xml
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //annotation支持
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

Mybatis使用SAX解析xml文件,parser.evalNode("/mapper")获取mapper节点的内容。




            select if(max(id) is null ,1,max(id)+2) as newId from user
        




依次解析如下的每一个一级节点,XMLMapperBuilder#configurationElement将Xml配置转换为Java对象。

cache-ref 
cache
/mapper/parameterMap
/mapper/resultMap
/mapper/sql
select|insert|update|delete
#org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        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"));
      //resultMap的解析
      resultMapElements(context.evalNodes("/mapper/resultMap"));
     //sql标签的解析
      sqlElement(context.evalNodes("/mapper/sql"));
     //select 等标签的解析      
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);
    }
  }

ResultMap节点解析

重点关注一下resultMap节点的解析,Mybatis的对ResultMap的解析步骤如下

  1. 遍历XML中所有的ResultMap节点
  2. 读取ResultMap节点的属性
  3. 遍历ResultMap的子节点,将数据库的每一列的映射关系解析成一个ResultMapping对象,添加到集合resultMappings中
  4. 将resultMappings封装到ResultMap中,构造ResultMap对象
  5. 以ResultMap.getId()为key,将ResultMap添加到Configuration.resultMaps中
  • 读取节点数据
    由于一个mapper中可以含有多个resultMap 所以resultMapElements(context.evalNodes("/mapper/resultMap"));context.evalNodes("/mapper/resultMap")的解析结果其实是一个集合,如下:
[




]
  • 生成ResultMapping
    开始具体的解析过程,读取resultMap 的子节点生成resultMapping对象,将所有的ResultMapping存放到一个List集合resultMappings中。
#org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElements
//遍历所有的resultMap节点
private void resultMapElements(List list) throws Exception {
   for (XNode resultMapNode : list) {
     try {
       resultMapElement(resultMapNode);
     } catch (IncompleteElementException e) {
       // ignore, it will be retried
     }
   }
 }
 private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
   return resultMapElement(resultMapNode, Collections. emptyList());
 }

 private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception {
   ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
   //读取resultMap节点的属性
   String id = resultMapNode.getStringAttribute("id",
       resultMapNode.getValueBasedIdentifier());
   String type = resultMapNode.getStringAttribute("type",
       resultMapNode.getStringAttribute("ofType",
           resultMapNode.getStringAttribute("resultType",
               resultMapNode.getStringAttribute("javaType"))));
   String extend = resultMapNode.getStringAttribute("extends");
   Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
   Class typeClass = resolveClass(type);
   Discriminator discriminator = null;
   List resultMappings = new ArrayList();
   resultMappings.addAll(additionalResultMappings);
   List resultChildren = resultMapNode.getChildren();
   // 遍历resultMap的子节点,数据库的每一列的映射关系解析成resultMapping对象
   for (XNode resultChild : resultChildren) {
     if ("constructor".equals(resultChild.getName())) {
       processConstructorElement(resultChild, typeClass, resultMappings);
     } else if ("discriminator".equals(resultChild.getName())) {
       discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
     } else {
       List flags = new ArrayList();
       if ("id".equals(resultChild.getName())) {
         flags.add(ResultFlag.ID);
       }
       //解析resultMapping
       resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
     }
   }
   //将resultMappings封装到ResultMap中
   ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
   try {
     return resultMapResolver.resolve();
   } catch (IncompleteElementException  e) {
     configuration.addIncompleteResultMap(resultMapResolver);
     throw e;
   }
 }

XMLMapperBuilder#buildResultMappingFromContext将每一个解析成一个ResultMapping

  private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) throws Exception {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections. emptyList()));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class> typeHandlerClass = (Class>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }
  • 生成ResultMap并加入Configuration
    实际上是加入了Configuration.resultMaps这个map中,key默认是xml中的id+当前的namespace
#org.apache.ibatis.builder.ResultMapResolver#resolve
  public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }
#org.apache.ibatis.builder.MapperBuilderAssistant#addResultMap
public ResultMap addResultMap(
      String id,
      Class type,
      String extend,
      Discriminator discriminator,
      List resultMappings,
      Boolean autoMapping) {
    ......
  //获取该resultMap 的id,默认是xml中的id+namespace
    id = applyCurrentNamespace(id, false);
  //创建ResultMap对象
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
     //将resultMap添加到configuration中
    configuration.addResultMap(resultMap);
    return resultMap;
  }

#org.apache.ibatis.session.Configuration#addResultMap
 protected final Map resultMaps = new StrictMap("Result Maps collection");
 public void addResultMap(ResultMap rm) {
    resultMaps.put(rm.getId(), rm);
    checkLocallyForDiscriminatedNestedResultMaps(rm);
    checkGloballyForDiscriminatedNestedResultMaps(rm);
  }

select|insert|update|delete 标签的解析

  1. 解析select|insert|update|delete等xml标签生成MixedSqlNode;
  2. 封装SqlNode到sqlSource中。SqlSource实际上只是对SqlNode的封装并没有实际生成Sql语句,因为MyBatis的动态Sql是根据查询条件动态拼接生成最终数据库执行的Sql;
  3. 根据解析到的sqlSource,resultMap等配置创建MappedStatement并加入Configuration中,实际上加入了Configuration.mappedStatements这个map中。每一个select|insert|update|delete节点都对应一个MappedStatement。

使用buildStatementFromContext(context.evalNodes("select|insert|update|delete"));来遍历并解析解析mapper中的每一个select|insert|update|delete节点

#org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List)`
private void buildStatementFromContext(List list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }
  private void buildStatementFromContext(List 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);
      }
    }
  }

 public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    //匹配databaseId,如果不匹配则不解析该sql
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    //获取resultType对应的Class
    Class resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    //节点名称select|insert|update|delete
    String nodeName = context.getNode().getNodeName();
   //sqlCommandType 是指sql类型,如insert,update等
    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);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    //解析selectkey标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre:  and  were parsed and removed)
    //提取sqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    //生成MappedStatement并加入configuration中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
  • 解析Sql类型
    上面的代码中已经解析出了当前sql的类型select|insert|update|delete
#org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

public enum SqlCommandType {
  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
  • 解析SqlNode生成SqlSource
    SqlSource实际上只是对SqlNode的封装,并没有实际生成Sql语句,因为MyBatis的动态Sql是根据查询条件动态拼接生成最终数据库执行的Sql;
#org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//XMLLanguageDriver#createSqlSource(Configuration, XNode, java.lang.Class)
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

#org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
    //解析xml节点封装成MixedSqlNode 
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    //生成sqlSource
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
  • 解析SqlNode
    将sql中的每一个节点都解析为一个特定的SqlNode,并判断子节点是否是普通的文本节点,如果是动态节点(包含${})则封装成TextSqlNode如果不是封装成StaticTextSqlNode
    如果不是普通文本节点则,则递归解析动态子节点
#org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
  protected MixedSqlNode parseDynamicTags(XNode node) {
    List contents = new ArrayList();
    NodeList children = node.getNode().getChildNodes();
   //遍历所有的子节点
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
 //判断子节点是否是普通的文本节点,如果是文本节点则解析结束,如果不是则递归解析动态子节点
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          //如果不包含${}则是静态文本节点
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        //寻找NodeHandler 
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        //处理子节点
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

上面的解析过程有一点复杂,我们用下面的sql来看一下它的解析过程


        update user
        
            
                name = #{name},
            
            
                age = #{age}
            
        
        where id = #{id}
    

parseDynamicTags方法中NodeList children = node.getNode().getChildNodes();其实拿到的是上面sql的3个子节点的内容

update user
name = #{name},age = #{age}
where id = #{id}

依次遍历三个子节点,根据判定条件封装成不同的sqlNode对象


【MyBatis】mapper.xml解析及annotation支持源码分析_第2张图片
SqlNode.png

sqlNode解析步骤

  1. update user
    判断child.getNode().getNodeType() == Node.TEXT_NODE成立,即为文本节点;然后调用textSqlNode.isDynamic()去判断该节点是否是动态的,这条语句中不包含${}所以不是动态的(GenericTokenParser("${", "}", handler)),最终会调用contents.add(new StaticTextSqlNode(data));


  2. child.getNode().getNodeType() == Node.ELEMENT_NODE这个判断是成立,即是它动态节点;然后根据nodeName从nodeHandlerMap查找nodeHandler并调用 handler.handleNode(child, contents);进行动态节点解析。

NodeHandler.png
//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#initNodeHandlerMap
  private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }

这里的nodeName就是set,所以会查找到SetHandler来解析,看一下它的解析方法

//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.SetHandler#handleNode
@Override
    public void handleNode(XNode nodeToHandle, List targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
      targetContents.add(set);
    }

这里又递归调用了parseDynamicTags()解析set节点的子节点,并封装成MixedSqlNode返回。可以预期在这次调用parseDynamicTags()中会有2个if子节点,然后每个if节点又会调用IfHandler.handleNode解析子元素,if的子节点只有文本节点所以解析终止返回一个文本节点,最终一层一层返回。

//org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.IfHandler#handleNode
 @Override
    public void handleNode(XNode nodeToHandle, List targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
       //读取if节点的test属性
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  1. where
    也判断child.getNode().getNodeType() == Node.TEXT_NODE成立,即为文本节点,会调用contents.add(new StaticTextSqlNode(data));最终会都会加入contents并返回new MixedSqlNode(contents);

所以最终解析结果,如下:

sql语句 节点类型
update user StaticTextSqlNode
SetSqlNode
IfSqlNode
name = #{name}, StaticTextSqlNode
IfSqlNode
age = #{age} StaticTextSqlNode
where id = #{id} StaticTextSqlNode

调试结果也正是这样


【MyBatis】mapper.xml解析及annotation支持源码分析_第3张图片
image.png

接下来通过new DynamicSqlSource(configuration, rootSqlNode);封装成SqlSource。
最后将insert|update等解析结果封装成MappedStatement并加入Configuration中builderAssistant.addMappedStatement(...);

//MapperBuilderAssistant#addMappedStatement(.....)
public MappedStatement addMappedStatement(...) {
......
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        //获取resultMap
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

注解支持

前面分析了configurationElement(parser.evalNode("/mapper"));解析mapper.xml。MyBatis其实也提供注解支持。

MyBatis 常用注解

  1. @SelectProvider
    动态调用某类的某个方法,并使用其返回值作为sql语句
  2. @Select
    使用注解value值作为sql语句
  3. @Select({""})
    动态解析value值生成动态Sql
  4. @Options
    该MappedStatement的配置参数,如:cache timeout等
    具体使用可以参考【MyBatis】基本使用

注解使用示例:

public interface UserMapper {
    public void insertUser(User user);
    public User getUser(Integer id);
    public void updateUser(User user);
    public void deleteUser(Integer id);

    /**
     * 标识提供sql语句的类和方法,mybatis会调用该类的方法获取sql语句,生成MappedStatement注册
     */
    @SelectProvider(type = SqlProvider.class,method = "getQueryUserSql")
    public User queryUserWithSqlProvider(@Param("id") Integer id);

    /**
     * 使用注解配置sql语句
     */
    @Select("select * from user where id = #{id}")
    public User queryUserWithAnnotation(@Param("id") Integer id);

    /**
     * 添加"})
    public void updateUserWithAnnotation(@Param("id") Integer id,@Param("name")String name,@Param("age")Integer age);

}

回到一开始解析mapper.xml的入口的地方 XMLMapperBuilder#parse ,其中的 bindMapperForNamespace();就是提供注解支持的入口。首先获取mapper.xml的Namespace,根据nameSpace的值加载Class对象(其实是一个接口)。我们要处理的对象就是该接口里所有方法的注解。

#org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //解析mapper.xml
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //annotation支持
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

private void bindMapperForNamespace() {
    //获取当前的Namespace,根据nameSpace的值生成Class,其实是Mapper接口的Class
    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) {
        //判断是否存储了mapper工厂
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          //将class加入Configuration,里面会有对mapper接口的解析
          configuration.addMapper(boundType);
        }
      }
    }
  }

configuration.addMapper(boundType)中提供了对注解mapper的解析,使用MapperAnnotationBuilder.parse()来对该接口的方法上的annotation进行解析,而且使用knownMappers来保存mapper代理工厂,key就是这个mapper接口类型(nameSpace)。这个MapperProxyFactory会用来产生动态代理bean(注入到Spring),当我们调用bean的方法的时候就会回调代理的invoke方法,在该方法中查找mappedStatement来组装sql并调用。

//org.apache.ibatis.session.Configuration#addMapper
 public  void addMapper(Class type) {
    mapperRegistry.addMapper(type);
  }
//org.apache.ibatis.binding.MapperRegistry#addMapper
public  void addMapper(Class 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);
        }
      }
    }
  }

具体的解析过程

  1. 遍历接口的每一个方法
  2. 首先根据方法注解创建出一个SqlSource。因为涉及到动态sql的解析,所以这一步也比较复杂;
  3. 然后获取可以识别的注解,提取注解配置的属性到对应的Java对象中;
  4. 最终生成一个MappedStatement并注册到Configuration中;
public class MapperAnnotationBuilder {
//org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
 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);
  }
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 {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

  void parseStatement(Method method) {
    Class parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    //根据注解创建SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      //解析@Options 
      Options options = method.getAnnotation(Options.class);
      ......
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        //解析@SelectKey
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        ......
      } else {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != null) {
        ......
      }

      String resultMapId = null;
      //解析@ResultMap
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
     ......
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }
      assistant.addMappedStatement(......);
     
    }
  }
}

从注解中生成SqlSource的过程,分为两种情况:
一种是 @Select @Insert @Update @Delete注解
一种是使用SelectSqlProvider、InsertSqlProvider、UpdateSqlProvider 、DeleteSqlProvider提供Sql语句 。

//从Annotation中提取Sql语句并交给XMLLanguageDriver解析
private SqlSource getSqlSourceFromAnnotations(Method method, Class parameterType, LanguageDriver languageDriver) {
    try {
      Class sqlAnnotationType = getSqlAnnotationType(method);
      Class sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      if (sqlAnnotationType != null) {
       //如果是 Select.class Insert.class Update.class Delete.class 则直接将value拿去解析
        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) {
         //如果是 SelectSqlProvider.class InsertSqlProvider.class UpdateSqlProvider.class DeleteSqlProvider.class 等类型则封装成ProviderSqlSource
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
      }
      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().trim(), parameterTypeClass);
  }
  • 如果是@Select @Insert @Update @Delete则直接将value拿去XMLLanguageDriver 解析,会判断如果sql是以

你可能感兴趣的:(【MyBatis】mapper.xml解析及annotation支持源码分析)