mybatis学习十一(核心流程-初始化阶段)

目录

一、mybatis核心流程

二、初始化阶段

2.1  建造者模式(Builder Pattern)概念

2.2  四个角色

2.3  使用场景

2.4  建造者模式和工厂模式的区别

三、Mybatis建造者类图

四、解析流程


一、mybatis核心流程

我们将mybatis启动分为三个阶段,分别为

初始化阶段:该阶段主要工作,将核心配置文件mybatis-config.xml和所有的mapper.xml进行读取,并保存到配置对象Configuration中

代理阶段:该阶段主要工作,对mapper接口进行增强,使我们能够面向接口编程

数据读写阶段:该阶段主要工作,解析sql并执行

二、初始化阶段

配置文件mybatis-config.xml比较简单,很容易解析

mapper.xml相对来说就很复杂,涉及映射关系,sql语句等等

在这里mybatis使用了建造者模式来完成初始化阶段

2.1  建造者模式(Builder Pattern)概念

使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

2.2  四个角色

Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建

ConcreteBuilder:实现Builder接口,针对不同的业务逻辑,具体化复杂对象的各部分的创建。在建造过程完成后,提供产品的实例

Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建

Product:要创建的复杂对象

2.3  使用场景

需要生成的对象具有复杂的内部结构,实例化对象时要屏蔽掉对象内部的细节,让上层代码与复杂对象的实例化过程解耦,可以使用建造者模式;简而言之,如果“遇到多个构造器参数时要考虑用构建器”

一个对象的实例化是依赖各个组件的产生以及装配顺序,关注的是一步一步地组装出目标对象,可以使用建造器模式

2.4  建造者模式和工厂模式的区别

1)对象复杂度

建造者建造的对象更加复杂,是一个复合产品,它由各个部件复合而成,部件不同产品对象不同,生成的产品粒度细;

在工厂方法模式里,我们关注的是一个产品整体,无须关心产品的各部分是如何创建出来的;

2)客户端参与程度

建造者模式,导演对象参与了产品的创建,决定了产品的类型和内容,参与度高;适合实例化对象时属性变化

频繁的场景;

工厂模式,客户端对产品的创建过程参与度低,对象实例化时属性值相对比较固定

三、Mybatis建造者类图

XMLConfigBuilder: 负责解析mybatis-config.xml

XMLMapperBuilder: 负责解析mapper.xml文件中的映射关系

XMLStatementBuilder: 负责解析mapper.xml文件中的SQL语句

就是通过上面三个Builder将mybatis的核心配置文件以及所有的Mapper.xml文件进行解析,并将信息封装到配置对象Configuration类(Configuration是单例的,生命周期是应用级)中。

四、解析流程

我们开始解析源码,之前我们写过

	@Before
	public void init() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		// 1.读取mybatis配置文件创SqlSessionFactory
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		inputStream.close();
	}

我们调用了SqlSessionFactoryBuilder()的build(inputStream)

XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
 return build(parser.parse());

Build方法中就创建了XMLConfigBuilder对象,同时调用了它的parse()

 parseConfiguration(parser.evalNode("/configuration"));

Configuration就是我们mybatis-config.xml配置文件的根节点

private void parseConfiguration(XNode root) {
    try {
      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"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

从根节点开始解析,我们可以看到properties,typeAliases,plugins等都是mybatis-config.xml配置文件的节点,每个节点使用一个方法去解析,解析完后,将信息保存到configuration配置对象中区,以propertiesElement(root.evalNode("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);
    }
  }

其他节点比较类似,就不一一介绍,我们来看最后一个mapper节点

mapperElement(root.evalNode("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.");
          }
        }
      }
    }
  }

我们可以看出,该方法中对每一个mapper文件进行遍历,同时进行了一些判断,并创建XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());

调用parse方法去解析mapper.xml文件中的映射关系

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

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

configurationElement方法就是用来解析mapper.xml

源码如下

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. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

其中resultMapElements(context.evalNodes("/mapper/resultMap"));就是解析resultMap节点

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    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<ResultMapping> resultMappings = new ArrayList<>();
    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<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

最终通过上述方法完成对mapper.xml文件中映射关系的解析,并保存到Configuration配置对象中

然后通过buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

在其中创建

final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);

使用XMLStatementBuilder完成对sql语句的解析

最终都保存到Configuration配置对象中

总结:mybatis的初始化阶段就是解析配置文件

通过三个builder进行解析,三个解析器负责不同的部分,解析到那部分,使用建造者模式创建该部分对象,最终都保存到Configuration配置对象中,完成初始化阶段

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值