MyBatis SQL解析源码

MyBatis 架构

MyBatista架构

  • 1、mybatis配置SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
    mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

  • 2、通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂。

  • 3、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

  • 4、mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

  • 5、Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

  • 6、Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

  • 7、Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

上述内容就是一个完整的MyBatis的Sql语句执行流程。在此基础上我们深入了解下关于XML解析的内容

Mybatis XMl解析 源码

mybatis执行主要依赖SqlSessionFacotry,而如何加载配置文件并创建SqlSessionFactory成为关键,这主要通过SqlSessionFactoryBuilder完成创建。

String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactoryBuilder

翻看SqlSessionFactoryBuilder源码,几乎所有的build方法重载都会调用下面的方法,只是传输流的不同。

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

每个build创建方法都会创建XMLConfigBuilder类,而这个类主要就是读取SqlMapConfig.xml的mybatis主配置类,进入该类查看源码。

XMLConfigBuilder
  • 构造
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

构造函数,在构造该类过程中会创建两个类XPathParser(xml解析器)和Configuration(文件配置类),所有的配置都依赖Configuration类。
在看该类的parser方法

  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")主要是解析表达式并返回XNode对象,关于XNode后面再看。
再看parseConfiguration方法完成的内容

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(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"));
	  // 解析mapper文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

该方法主要就是对Config.xml文件配置读取,并为Configuration对象添加属性。
接下来重点关注

mapperElement(root.evalNode("mappers"));

这里对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四种不同的配置方式去获取mapper文件或接口文件。这里创建了XMLMapperBuilder类,它完成对mapper文件的解析

XMLMapperBuilder
  • 构造方法
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

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

这里主要创建了XPathParser类和MapperBuildAssistant类,前者已经讲过是解析类,而MapperBuildAssistant,这是mapper解析中的关键,他作为mapper解析的助理类,负责将解析出来的结果保存下来,并且通过configuration.addMappedStatement方法保存到conguration对象中,最终在调用dao是根据传入的参数动态生成sql

  public void parse() {
	// 判断是否已经加载
    if (!configuration.isResourceLoaded(resource)) {
      // 解析mapper文件
      configurationElement(parser.evalNode("/mapper"));
	  // 置为加载
      configuration.addLoadedResource(resource);
	  // 绑定mapper接口
      bindMapperForNamespace();
    }

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

在看下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"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
	  // 解析sql标签
      sqlElement(context.evalNodes("/mapper/sql"));
	  // 解析crud标签
      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);
    }
  }

看下如何解析CRUD标签

  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类,继续看下去

XMLStatementBuilder
  • 构造
  public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
    super(configuration);
    this.builderAssistant = builderAssistant;
    this.context = context;
    this.requiredDatabaseId = databaseId;
  }

构造方法没有很特别,再看parseStatementNode()方法,代码太长只粘贴关键部分

 public void parseStatementNode() {
	......

    // 解析include标签
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
	// 将sql标签内容替换掉include标签
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
	// 解析sql为返回SqlSource对象
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    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);
  }

在Mybatis中,每一个select|insert|update|delete标签,都会被解析为一个MappedStatement对象,SqlSource就是MappedStatement对象中一个属性,其最终执行的sql字符串就是由SqlSource提供的。

SqlSource实现类:

  • DynamicSqlSource:处理动态sql。
  • RawSqlSource:处理静态sql,其内部装饰StaticSqlSource。
  • StaticSqlSource:处理静态sql,无论是静态sql,还是动态sql,最终的处理结果,都是静态sql。
  • ProviderSqlSource:处理注解Annotation形式的sql。

关键解析Sql语句主要是

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

查看LanguageDriver调用实现类XMLLanguageDriver方法

  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

这里创建了XMLScriptBuilder类,他主要对sql语句进行解析

XMLScriptBuilder
  • 构造方法
  public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
  }
  • parseScriptNode()方法
  public SqlSource parseScriptNode() {
	// 解析XNode生成sqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
	// 判断是否为动态sql,返回不同的sqlSource
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

sqlNode是标签中的sql节点信息,不同的节点信息SQLnode类型不同

MixedSqlNode:意为混合的SqlNode,它保存了其他多种SqlNode的集合,可以看做是一个List列表,事实也确实如此。
下面的方法主要就完成了对SQL语句的解析

protected MixedSqlNode 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 = 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);
  }

返回MixedSqlNode封装到SQLSource中

这样就完成了sql的解析

总结

  • SqSessionFactoryBuilder:读取配置创建sqlSessionFactory

  • BaseBuilder:作为其他Builder类的基类

  • XMLConfigBuilder:在Configuration解析时介绍过,主要用来解析config配置文件下的Configuration节点,内部会使用XMLMapperBuilder用于解析xml文件

  • XMLMapperBuilder:在Mapper解析中也介绍过。主要用来解析Mapper文件的,里面用,内部会使用XMLStatementBuilder来处理节点

  • XMLStatementBuilder:解析select|insert|update|delete节点,内部会使用XMLScriptBuilder解析xml节点

  • XMLScriptBuilder:解析sql中各个其他的节点并把解析结果保存到SqlNode中。

  • MapperBuilderAssistant:这是mapper解析中的关键,他作为mapper解析的助理类,负责将解析出来的结果保存下来,并且通过configuration.addMappedStatement方法保存到conguration对象中,最终在调用dao是根据传入的参数动态生成sql。所以说configuration是MyBatis调用的调配中心。

  • LanguageDriver:是一个辅助工具类,用于创建SqlSource。

  • XMLLanguageDriver:用于创建动态、静态SqlSource。

  • RawLanguageDriver:在确保只有静态sql时,可以使用,不得含有任何动态sql的内容,否则,请使用XMLLanguageDriver

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值