(三)MyBatis源码解析之配置文件

原创 2016年05月31日 20:05:50

这篇文章我们来看看最重要的mappers标签是怎样解析的,Mybatis在mappers标签中引入所有的***Mapper.xml

	<mappers>
		<mapper resource="sqlMap/messageMapper.xml" />
	</mappers>

Mybatis支持通过包和具体的xml文件来引入Mapper,如果是向上面那样通过xml文件来引入Mapper的话,Mybatis还可以分别支持resource、url、classs三种方式,我们一般都是通过resource="..."的形式来引入具体的Mapper。

  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.");
          }
        }
      }
    }
  }
Mapper的解析是从XMLMapperBuilder的configurationElement方法开始的,这里会分别解析Mapper的所有标签,包括cache-ref,cache,/mapper/parameterMap,/mapper/resultMap,/mapper/sql,select | insert | update | delete标签,其中/mapper/resultMap,/mapper/sql,select | insert | update | delete标签是必须的

  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"));
      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. Cause: " + e, e);
    }
  }
首先每个Mapper中都必须配置namespace属性,namespace是MyBatis面向接口编程的时候必须的标签,根据namespace就可以将接口文件和Mapper文件建立一对一的对应关系,这也就是为什么我们不需要有具体的实现类就可以进行数据库操作的原因,至于具体是怎么实现的,后面会有专门的文章进行介绍

这里只介绍最有代表意义的resultMap标签和select | insert | update | delete标签

resultMap标签

result配置如下

	<resultMap type="net.klq.bean.Message" id="message">
		<id column="id" jdbcType="INTEGER" property="id" />
		<result column="command" jdbcType="VARCHAR" property="command" />
		<result column="description" jdbcType="VARCHAR" property="description" />
		<result column="content" jdbcType="VARCHAR" property="content" />
	</resultMap>

resultMapElement(...)方法负责将resultMap标签和其子标签中的所有内容解析出来存放到resultMapping中,最后通过ResultMapResolver对象将所有resultMap的信息都保存到configuration对象的resultMap属性中,该属性的类型是Map,将resultMap标签存放到该属性中时以namespace.id的形式存放。值即为该resultMap中包含的所有信息

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    System.err.println("ValueBasedIdentifier:"+resultMapNode.getValueBasedIdentifier());
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    System.err.println("id:"+id);
    //属性的值为null则返回null,不为null则返回属性对应的值
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //根据type的全路径名创建一个class对象
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    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<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        //每一个result标签的信息会存储到一个resultMapping对象中,resultMap标签下所有的result和id标签都方法哦resultMappings中
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();//resolve方法会将resultMap放到configuration中
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
select | insert | update | delete标签

该标签是mybatis所有标签中最复杂的
在mybatis中由XMLStatementBuilder类负责该标签的解析工作,解析该标签的方法如下,这里还是将select | insert | update | delete标签的所有信息解析出来存放到configuration对象的MappedStatement属性中,select | insert | update | delete标签的属性很多,在以后具体使用时再讲解每个属性的作用,在这些标签中最重要的就是动态sql的解析了,在代码1处开始动态标签的解析也就是具体sql的解析,下面来看看动态标签是如何被解析的

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

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

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //将其设置为 true,将会导致本条语句的结果被二级缓存
    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.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// 1
    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))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
动态标签的解析工作是由XMLScriptBuilder类来负责的,解析sql动态标签很好的利用了java的多态的特性,在mybatis中由下面这些类组成了mybatis丰富的动态标签,在一条动态sql中,对应的动态标签的内容会存放到对应的***SqlNode类中



让我们看看源码是如何解析动态标签的,首先拿到一个动态sql,将sql按标签进行分割,通过for循环来判断每一个标签,如果该标签是文本节点,那么判断是否是动态文本节点,如果是动态文本节点则创建一个TextSqlNode对象将该节点信息存储到其中,然后将该对象放到list中,如果不是动态文本节点则创建一个StaticTextSqlNode对象将节点信息存储到其中。如果该标签是动态标签节点,则根据标签名创建对应的标签对象,然后解析动态标签节点

  List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    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 handler = nodeHandlers(nodeName);//根据标签名创建对应的标签对象
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);//解析动态标签节点
        isDynamic = true;
      }
    }
    return contents;
  }
IfHandler的handleNode(...)实现如下,在该方法中递归的调用parseDynamicTags(...)方法,直到将所有动态标签的文本内容解析出来为止,这就是该递归的结束条件,所有标签都解析完成后将这些标签的信息存储到MixedSqlNode中,MixedSqlNode是MappedStatement的一个属性,这样select | insert | update | delete标签的所有信息就都存储到了configuration对象的MappedStatement属性中了

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  }


版权声明:本文为博主原创文章,未经博主允许不得转载。

《深入理解mybatis原理》 Mybatis初始化机制详解

对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外。本章将通过以下几点详细介绍MyBatis的初始化过程。 1.MyBatis的初始化做了什么 ...

Mybatis源码解析优秀博文

最近阅读了许久的mybatis源码,小有所悟。同时也发现网上有许多优秀的mybatis源码讲解博文。本人打算把自己阅读过的、觉得不错的一些博文列出来。以此进一步加深对mybatis框架的理解。其实还有...
  • nmgrd
  • nmgrd
  • 2017年01月19日 01:10
  • 1059

Mybatis3源码分析(22)-总结

Configuration加载过程 Configuration组成 基本属性。在mybatis-config.xml加载的属性这里定义为基本属性。如cacheEnabled/variables...

《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例,...

Mybatis3源码分析(一):从sqlSession说起

分析MyBaits3的源码首先得从sqlSessionFactory开始,先来看一段spring配置文件中Mybaits的sqlSessionFactory的配置。 ...

mybatis源码解读(3)

selectOne和selectListString resource = "mybatis-config.xml"; InputStream inputStream = Resources.getR...

MyBatis架构设计及源代码分析系列(一):MyBatis架构

如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处。 一、概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 T...

Mybatis源码分析一(SqlsessionFactory及源码整体结构)

搞java的想提高自己的姿势水平,想拿高工资,对常用开源框架的深入了解是必不可少的,想深入了解源码分析更是必不可少的,今天我开始对mybatis的源码进行分析,并做点记录以备查验。开源框架研究,文档的...

MyBatis架构设计及源代码分析系列(一):MyBatis架构

如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处。 一、概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The My...

mybatis源码解读(8)

mybatis plugin
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:(三)MyBatis源码解析之配置文件
举报原因:
原因补充:

(最多只允许输入30个字)