zoukankan      html  css  js  c++  java
  • mybatis随笔一之SqlSessionFactoryBuilder

    SqlSessionFactoryBuilder是构建sqlSessionFactory的入口类

    从该类的方法可知,它是通过不同的入参来构造SqlSessionFactory,除了最后一个configuration入参方法外,其余方法最终都调用如下方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    如上图所示,先创建了一个XMLConfigBuilder类的实例
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    在这个初始化过程中,首先创建了XMLMapperEntityResolver类的实例,这个类顾名思义是个实体mapper文件实体解析器,里面有个map将mybatis的xml文件与对应的解析文件关系保存起来,初始化后再实例化XPathParser
    public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonConstructor(validation, variables, entityResolver);
        this.document = createDocument(new InputSource(inputStream));
      }
    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
        this.validation = validation;
        this.entityResolver = entityResolver;
        this.variables = variables;
        XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();
      }
    commonConstructor比较简单,就是给一些类变量赋值,并且初始化了一个xpath对象,这里使用了工厂设计模式。
    new InputSource(inputStream)就是将输入流赋予自己内部的一个变量byteStream。
    createDocument(new InputSource(inputStream))这里面主要做了这几件事通过DocumentBuilderFactory生成DocumentBuilder,并将entityResolver赋给它的属性字段同时定义了一个处理异常的内部类,
    然后通过builder.parse(inputSource)将输入流解析成一个document对象。
    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;
      }
    new Configuration()主要是在构造方法里面注册了别名
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    registerAlias是TypeAliasRegistry类的方法,将别名与类的关系保存在了内部的private final Map<String, Class<?>> TYPE_ALIASES
    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
      }
    super(new Configuration())就是将相应的变量保存在父类属性上,方便其它子类使用。
    ErrorContext.instance()方法内部采用的threadlocal方式,每个线程都持有一个ErrorContext对象resource("SQL Mapper Configuration")就是一个简单的赋值给resource变量方法。
    至此一个XMLConfigBuilder对象就创建出来了,然后就进入了build(parser.parse())。
    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")在初始化过程中已经将mybatis_config.xml文件解析成document对象,这里使用xpath解析工具将configuration节点解析成一个XNode对象。

    private void parseConfiguration(XNode root) {
        try {
          Properties settings = settingsAsPropertiess(root.evalNode("settings"));
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          loadCustomVfs(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectionFactoryElement(root.evalNode("reflectionFactory"));
          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);
        }
      }
    该方法一看便知是扫描configuration节点下的诸如properties、typeAliases节点做相应的处理。稍微分析其中几个方法
    propertiesElement(root.evalNode("properties"))是读取properties节点下的property子元素以及resource指向的资源文件路径,将两者合并为一个properties并保存到内部的vars变量。
    typeAliasesElement(root.evalNode("typeAliases"))读取子元素时首先判断是否package,是的话扫描包下的非接口、匿名、内部类注册别名。
    pluginElement(root.evalNode("plugins"))将插件加到插件链上。
    重点提一下最后的mapperElement方法,这个方法内首先判断子元素是不是package,若是<mapper resource="mapper/demo.xml"/>这种形式的。其中最重要的是下面两行代码
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    mapperParser.parse();
    构建XMLMapperBuilder对象前面已经分析过了,重点分析parse方法
    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
      }
    private void bindMapperForNamespace() {
        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) {
            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);
              configuration.addMapper(boundType);
            }
          }
        }
      }
    该方法获取mapper文件的namespace来实例化mapper接口,这也就解释了为什么namespace要和mapper接口全路径一致。
    其中值得注意的是configuration.addMapper(boundType)方法,该方法调用了mapperRegistry.addMapper(type)方法。
    public <T> void addMapper(Class<T> 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<T>(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);
            }
          }
        }
      }
    MapperProxyFactory是个代理接口方法工厂,后面会使用到它,就是通过它来给没有实现类的mapper接口代理。
    重点看其中的parser.parse()方法
    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();
      }
    先得到接口里的methods,然后遍历解析方法,查看该方法有没有注释sql语句有的话拼接这些语句,因为现在流行使用xml文件配置方式更加灵活的处理sql语句,因此这里跳过。
    parsePendingMethods方法是有些insert或者update的select语句引入了sql片段,但是sql片段还没解析到,因此先将这些方法pend,后面每有新的mapper解析时都会尝试解析完这些pend方法。
    同理parsePendingResultMaps(),parsePendingChacheRefs(),parsePendingStatements()三个方法也类似。
    现在configuration已经基本解析完成,然后调用
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    返回一个默认的SqlSessionFactory实现,其内部持有一个Configuration变量。

    至此SqlSessionFactoryBuilder创建DefaultSqlSessionFactory的过程完成。
    
    
     



  • 相关阅读:
    在web项目中使用配置文件
    javaFx教程 scene builder 加载第三方jar包
    getResource的两种用法
    tomcat部署工具
    withMisfireHandlingInstruction
    quartz教程三
    quartz教程二
    LocalDateTime与DateTimeFormatter
    quartz demo
    在小程序中点击后跳转到另一个小程序
  • 原文地址:https://www.cnblogs.com/tuifeideyouran/p/6393704.html
Copyright © 2011-2022 走看看