spring-mybatis-2 (mapper的xml解析和註解解析)

mybatis框架設計圖

在這裏插入圖片描述

mybatis的主要構件

  • SqlSession
    作爲MyBatis工作的主要頂層API,表示和數據庫交互的會話,完成必要數據庫增刪改查功能
  • Executor
    MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護
  • StatementHandler
    封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數、將Statement結果集轉換成List集合。
  • ParameterHandler
    負責對用戶傳遞的參數轉換成JDBC Statement 所需要的參數,
  • ResultSetHandler
    負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合;
  • TypeHandler
    負責java數據類型和jdbc數據類型之間的映射和轉換
  • MappedStatement
    MappedStatement維護了一條<select|update|delete|insert>節點的封裝,
  • SqlSource
    負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回
  • BoundSql
    表示動態生成的SQL語句以及相應的參數信息
  • Configuration
    MyBatis所有的配置信息都維持在Configuration對象之中。

mybatis mapper xml的解析

mybatis 加載入口
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet

  @Override
  public void afterPropertiesSet() throws Exception {
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory

//在默認的構造器中,會註冊很多別名處理器
 configuration = new Configuration();

  // mybatis 插件註冊
   if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }

// mybatis 全局配置文件解析
  if (xmlConfigBuilder != null) {
      try {
        // 全局配置文件解析
        xmlConfigBuilder.parse();
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    // mapper的xml解析 重點
    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 + "'");
        }
      }
    }

mapper xml 文件下的 resultMap 標籤的解析
ResultMap:將查詢結果集中的列一一映射到java對象的各個屬性上

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

// parser 就是XPathParser對象,使用該對象查找 mapper標籤節點
 configurationElement(parser.evalNode("/mapper"));

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

// context 就是mapper xml文件中的內容
// 解析 resultMap 標籤下的所有標籤
 resultMapElements(context.evalNodes("/mapper/resultMap"));

返回到
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

mapper xml 文件下的 sql 標籤的解析
sql片段:Sql中可將重複的sql提取出來,使用時用include引用即可,最終達到sql重用的目的

sqlElement(context.evalNodes("/mapper/sql"));

mapper xml 文件下的 crud 的標籤解析

 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

查看
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.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

 // 將 context 內容即sql內容解析後,分爲 動態的SqlNode和靜態的SqlNode
 List<SqlNode> contents = parseDynamicTags(context);

SqlNode
在這裏插入圖片描述

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags

List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    //sql的所有節點,即 ParentNode#getChildNodes,使用 ParentNode的實現類是 DeferredElementImpl
    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 {
          //解析到 sql 內容有靜態sql
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        //是動態標籤時,會走到這裏,例如: <where>
        String nodeName = child.getNode().getNodeName();
        //根據標籤名獲取對應的處理器,這裏的 nodeName 就是動態sql的標籤
        NodeHandler handler = nodeHandlers(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        //解析動態標籤,例如 <if>
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    // 看下面動態sql生成的結果 <if>
    return contents;
  }

  // 註冊了標籤對就的處理類
   NodeHandler nodeHandlers(String nodeName) {
    Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
    map.put("trim", new TrimHandler());
    map.put("where", new WhereHandler());
    map.put("set", new SetHandler());
    map.put("foreach", new ForEachHandler());
    map.put("if", new IfHandler());
    map.put("choose", new ChooseHandler());
    map.put("when", new IfHandler());
    map.put("otherwise", new OtherwiseHandler());
    map.put("bind", new BindHandler());
    return map.get(nodeName);
  }

==NodeHandler ==
在這裏插入圖片描述
if 標籤解析
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.IfHandler#handleNode

   @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      //這裏迭代調用 parseDynamicTags,解析動態標籤裏的 sql
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      //獲取 test 元素上的值,即表達式
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
<!--sql片段-->
    <sql id="query_user_where_sql">
        <if test="id!=null and id!=''">
             id=#{id}
        </if>
    </sql>
    <select id="selectUserById" resultType="com.dn.spring.mybatis.bean.UserDo">
        SELECT * from t_user
        <where>
            <include refid="query_user_where_sql"/>
        </where>
    </select>

上面的動態sql解析後,如下圖所示
在這裏插入圖片描述

回到
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode

public SqlSource parseScriptNode() {
    List<SqlNode> contents = parseDynamicTags(context);
    // MixedSqlNode 裏有很多元素,這些元素是分層次結構的,每個元素下面可能還有很多元素,如上圖所示的層次
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

回到
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

//將前面獲取到的 xml屬性,封裝成 MappedStatement 
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

MappedStatement 類

  • id
    mapper的命名空間 + sql語句的id
  • SqlSource
    sql已拼裝好了, 但是還沒有參數值
  • resultMaps
    sql 結果的映射關係也封裝在這個在類中
  • sqlCommandType
    當前標籤是sql的哪個類型

sqlSource內容
將 sql中的 #{ } 替換爲 是在 org.apache.ibatis.builder.SqlSourceBuilder#parse 中的 String sql = parser.parse(originalSql); 中做的
在這裏插入圖片描述
最終 會封裝到 org.apache.ibatis.session.Configuration#mappedStatements 屬性中,key就是mapper的命名空間 + sql語句的id ,value 就是 MappedStatement 對象.每個定義的sql都會有一個MappedStatement 對象

mybatis mapper 註解的解析

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

 if (!configuration.isResourceLoaded(resource)) {
      //mapper xml的解析
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // mapper 接口方法上 annotation的支持
      bindMapperForNamespace();
    }

org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
       // mapper 接口類
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        //查看該方法,根據key(mapper的接口類) 在org.apache.ibatis.binding.MapperRegistry#knownMappers容器中獲取值,
       // 值是 MapperProxyFactory
        if (!configuration.hasMapper(boundType)) {
          configuration.addLoadedResource("namespace:" + namespace);
          //初始化 org.apache.ibatis.binding.MapperRegistry#knownMappers 容器
          configuration.addMapper(boundType);
        }
      }
    }
  }

org.apache.ibatis.session.Configuration#addMapper

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

org.apache.ibatis.binding.MapperRegistry#addMapper

//容器存入信息
knownMappers.put(type, new MapperProxyFactory<T>(type));

//mapper接口類中的方法上的註解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();

查看構造器和parse方法
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse

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)) {
      //加載 mapper xml的配置文件
      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();
  }  

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement

// 解析方法上的註解後,將信息封裝成 SqlSource 
 SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#getSqlSourceFromAnnotations

      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);
         // 查看 buildSqlSourceFromStrings 方法
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
        //例如 @SelectProvider 註解的支持日解析
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
      }

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#buildSqlSourceFromStrings

 return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, java.lang.String, java.lang.Class<?>)

if (script.startsWith("<script>")) {
//方法上的註解,例如 @Select 的值是 <script>開頭的
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      // 使用XMLLanguageDriver#createSqlSource解析 ognl表達式
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
  }else {
      //走到這裏,則註解裏的值是純文本的
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
        // 封裝成 DynamicSqlSource
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }     

註解上也是支持 ognl表達式的,並且使用的是 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource的方式解析的

參考

Mybatis筆記 - SQL標籤方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章