MyBatis(技术NeiMu):核心处理层(MyBatis的初始化)

回顾

前面已经将基础支持层看完了,各种底层模块的功能

  • 类型转换
  • 参数解析器
  • 数据库连接池
  • 事务
  • 缓存

核心处理层

下面开始学习核心处理层

首先,核心处理层包括6个部分

  1. 配置解析
  2. 参数映射
  3. SQL解析
  4. SQL执行
  5. 结果集映射
  6. 插件

MyBatis初始化

首先先看第一个部分,配置解析,MyBatis跟Spring一样,MyBatis也有配置文件,因此也需要去解析配置文件,而在MyBatis中的配置文件主要有两个,分别是mybatis-config.xml和映射配置文件

建造者模式

建造者模式是用来将一类复杂对象的构建过程与它的表示分离,让相同的建造工程可以有不同的表示,比如A产品和B产品的建造流程是一样的,那么A产品和B产品就可以使用同一套建造流程!这时候就是使用相同的构建过程,但表示的产品不一致了

首先建造者模式需要三个对象

  • 建造者(接口):负责使用特定建造顺序去建造产品!
  • 导演:调用具体的建造者去创建产品,为建造者提供数据来源!
  • 产品

建造者模式的优点

  • 使用导演来构建出具体的产品,但构建的细节在建造者,所以导演根本不知道创建产品的具体细节,只负责构建所需的信息传递,实现了产品对象的上层代码和产品对象的解耦,最上层的指挥完全不知道产品的建造流程,我们不需要改动指挥的代码!!!
  • 建造者模式将复杂的产品的创建过程分散到了不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,同时让创建过程更加清晰,建造者将复杂产品的创建过程分开多个方法来创建,并且使用一个build方法来规定了先执行哪个方法,说白了就是先创建哪个部位
  • 每个具体的构造者有产品的每个部位的创建方法,因此一个具体的构建者是可以单独创建出完整的产品对象的,而具体的建造者之间是独立的,因此当我们新增产品时,去新添加建造者即可!然后指挥调用新的建造者来build即可!

构造者模式的缺点

  • 创建的产品要有共同点,组成部分相似,并且创建的流程要基本一致,如果产品很多、产品内部变化复杂、产品之间的差异性很大,是不适合使用建造者模式的,因为产品很多且内部变化复杂会导致建造者越来越多,而产品之间的差异性很大,也会导致建造者越来越多。。。。。。

初始化!BaseBuilder!

在MyBatis中,初始化过程就采用了建造者模式,说白了创建SqlSessionFactory的时候采用了建造者模式,具体的产品是SqlSessionFactory,指挥者是SqlSessionFactoryBuilder,那谁是建造者呢?看代码!

指挥者SqlSessionFactoryBuilder会调用build方法来调用建造者来进行创建SqlSessionFactory(产品),源码如下

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //建造者就是XMLConfigBuilder
        //而且注意,这里建造者建造的并不是直接的SqlSessionFactory
       	//创建出需要的建造者
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //build方法就是指挥者去调用建造者去创建产品了!
      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.
      }
    }
  }

可以看到,对于SqlSessionFactory这个产品的建造者就是XMLConfigBuilder对象,可能因为这个产品只有一款,所以这里没有使用接口

下面就来看一下MyBatis的建造者系列(XmlConfigBuilder是继承了BaseBuilder的,因此从BaseBuilder开始看起)

在这里插入图片描述
BaseBuilder其实就扮演着建造者接口的角,虽然其是一个抽象类,所有的具体建造者都是在其基础上建立的

三个关键成员属性

  • configuration:存储MyBatis的所有配置信息的
  • typeAliasRegistry:之前我们也看过了,这是类型别名注册中心(对Java类型取别名,比如java.lang.Integer转化为int)
  • typeHandlerRegistry:类型转换器注册中心(TypeHandler,用来将数据库类型转化为Java类型的)

而且从构造方法上可以看到,typeAliasRegistry和typeHandlerRegistry都是从Configuration来的,也就是从配置文件上来的!!!

在这里插入图片描述
现在BaseBuilder先看到这

XMLConfigBuilder

回到我们的XMLConfigBuilder,SqlSessionFactoryBuilder就是指挥XMLConfigBuilder来进行,先来看一下XMLConfigBuilder的结构

在这里插入图片描述
关键的属性

  • parsed:代表是否已经解析过mybatis-config.xml配置文件了(用改标志来让配置文件只被解析一次!!!)
  • XPathParser:用来解析mybatis-config.xml配置文件的XPathParser对象,前面已经看过了,其底层是解析DOM树的
  • environment:environment标签的default属性
  • ReflectorFactory:负责创建和缓存Reflector对象的,Reflector就是用来将行记录映射成Java Bean的

在这里插入图片描述
可以看到执行的是build方法,而在build方法之前先会执行构造者的parse方法,下面就来看看这个构造者如何创建产品的

源码如下

public Configuration parse() {
    //判断MyBatis文件是否已经解析过了
    if (parsed) {
        //如果已经解析过,抛错
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    //没有解析过,将标志位改为true,开始解析
    parsed = true;
    //在mybatis配置文件中找到configuration节点,并开始进行解析
    //之前已经分析过XPathParser的evalNode方法了,就是解析该标签节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  1. 判断是不是已经解析过了,如果解析过了,抛错
  2. 如果没有解析过,修改标志位,代表现在进行解析
  3. 使用XPathParser取解析configuration标签节点后,调用parseConfiguration去处理解析结果
parseConfiguration

该方法就是用来正式解析MyBatis总配置文件下有用的信息了,也就是configuration标签节点下的信息

在这里插入图片描述
该方法源码如下,可以看到,这就是parseConfiguration方法就是建造者的建造流程了!说得更具体一点是解析配置文件,然后按照建造流程去取配置文件里面的对应节点信息来创建Configuration的流程!!!

private void parseConfiguration(XNode root) {
  try {
    //解析configuration标签下的properties属性,组装Configuration的properties部分
    propertiesElement(root.evalNode("properties"));
    //获取configuration标签下的settings属性
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //从settings部分去设置vfsImpl字段(虚拟文件系统,之前提到过这个用来查找文件的!!)
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    //解析typeAliases标签,并组装Configuration的typeAliases部分
    typeAliasesElement(root.evalNode("typeAliases"));
    //解析plugins标签,并组装Configuration的plugins部分
    pluginElement(root.evalNode("plugins"));
    //解析objectFatory标签,并组装Configuration的objectFactory部分
    objectFactoryElement(root.evalNode("objectFactory"));
    //解析objectWrapperFactory标签,并组装Configuration的objectWrapperFactory部分
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    //解析reflectorFactory标签,并组装Configuration的reflectorFactory部分
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
     //将上面解析出的settings属性,组装Configuration的setting部分
    settingsElement(settings);
    //解析environments属性,组装Configuration的environments部分
    environmentsElement(root.evalNode("environments"));
    //解析databseIdProvider属性,组装Configuration的databaseIdProvider部分
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
     //解析typeHandlers属性,组装Configuration的typeHandlers部分
    typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers属性,组装Configuration的mappers部分
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

可以看到,其实XMLConfigBuilder建造的产品其实是Configuration,也就是解析出来的配置文件里面的内容,并且按照一定的顺序来解析配置文件里面的标签,然后进行组装到Configuration中,最后完成创建,也就是说对于Configuration这个产品来说已经被分成几部分来完成创建了,解析组装的顺序如下

  • properties标签
  • typeAlias标签
  • plugins标签
  • objectFactory标签
  • objectFactoryWrapper标签
  • settings标签
  • environments标签
  • databaseIdProvider标签
  • typeHandler标签
  • mapper标签

下面就看一下,MyBatis是如何对这几个标签节点进行解析的

解析Properties标签

对应的方法为propertiesElement,源码如下

先说明一下properties标签的作用,该标签的子标签其实就是用来记录一些配置信息的,比如数据库的连接信息,账号密码。。。。。。。

private void propertiesElement(XNode context) throws Exception {
    //判断properties标签节点是否为null
    //如果不为null
    if (context != null) {
        //获取properties下面的子标签,也就是所有property标签
      Properties defaults = context.getChildrenAsProperties();
        //解析properties里面的resource属性
      String resource = context.getStringAttribute("resource");
        //解析properties里面的url属性
      String url = context.getStringAttribute("url");
        //url和resource不能同时存在,同时存在会抛错
      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.");
      }
        //如果resource不为null
      if (resource != null) {
          //将解析resource结果存放进properties中
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
          //将解析url的结果存放进properties中
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
        //
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
        //将Property标签存放进XPather中
      parser.setVariables(defaults);
        //将Property标签存放进Configuration中
      configuration.setVariables(defaults);
    }
  }

可以看到,propertiesElement方法会解析配置文件中的properties节点,形成Properties对象(java.util包下的,可以知道使用了Java原生解析XML获取子标签的支持),然后将Properties对象设置到XPathParser和Configuration的variables属性中

并且从代码上可以知道,MyBatis不支持URL和Resouce同时使用!!!!!!

解析settings标签

对应的方法为settingsAsProperties,源码如下

先说明一下settings标签的作用,该标签里面的子标签其实配置的是MyBatis的全局性配置,这些配置会改变MyBatis的运行行为

private Properties settingsAsProperties(XNode context) {
    //判空
    if (context == null) {
      return new Properties();
    }
    //获取settings标签下的子标签,同样使用一个Properties对象存储
    Properties props = context.getChildrenAsProperties();
    // 创建Configuration对应的MetaClass对象
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    //返回settings的子标签
    return props;
  }

可以看到,在这里并没有仅仅只是解析了settings标签而已,并没有将其添加进Configuration对象中!返回了settings标签里面的子标签,要完成settings的所有子标签的解析,后面才会往Configuration中添加settings标签

加载vfs

前面我们已经认识过VFS了,就是Virtual File System,虚拟文件系统,可以用来根据路径找到文件的,对应加载的方法为loadCustomVfs,并且注意,这里传进来放封props参数是setting的子标签,因此可以知道,对于vfs的配置是在settings的子标签下完成的

private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    //获取vfsImpl标签
    String value = props.getProperty("vfsImpl");
    //如果不为空
    if (value != null) {
        //遍历指定的实现类然后添加进Configuration中
      String[] clazzes = value.split(",");
      for (String clazz : clazzes) {
        if (!clazz.isEmpty()) {
          @SuppressWarnings("unchecked")
          Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
          configuration.setVfsImpl(vfsImpl);
        }
      }
    }
  }

可以看到,vfsImpl标签是settings的子标签第一个被解析的,仅仅只是简单的获取vfsImpl子标签的值,然后遍历vfs指定的实现类,添加进Configuration中而已

加载logImpl

MyBatis可以自定义日志使用的实现类,具体的自定义也是在settings子标签下,具体就是logImpl子标签,当加载完vfs后就到加载日志了

  private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
  }

源码很简单,获取logImpl子标签然后根据值去解析出对应日志实现类的Class类型,然后添加进Configuration中

解析typeAlias标签

接下来到typeAlias标签,这个不属于settings标签内,typeAlias标签就是用来处理别名与实体类的映射关系的!!!

这里传进来得到parent是typeAlias标签节点,先来说一下typeAlias标签节点的子节点

  • package:package的name属性指定了包的路径,该包的路径下的所有类都创建了别名,默认以类名作为别名(真实的名字应该是全限定名)
  • alias:alias的alias属性指定了别名,type为指定类的全限定名
 private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        //获取里面的子节点
      for (XNode child : parent.getChildren()) {
          //判断是不是package节点
        if ("package".equals(child.getName())) {
           //获取里面的name属性!
          String typeAliasPackage = child.getStringAttribute("name");
            //从Configuration中获取typeAliasRegistry(别名注册中心)
            //执行registerAlias方法去注册
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
           //如果不是package节点
            //只能是alias节点了
            //获取alias属性和type属性
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
              //使用反射获取type属性指定的类Class对象
            Class<?> clazz = Resources.classForName(type);
              //如果别名为空
            if (alias == null) {
                //也会去注册,只不过此时的别名就是类名
              typeAliasRegistry.registerAlias(clazz);
            } else {
                //别名不为空,使用指定的别名进行注册
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

可以看到typeAlias标签的完整解析,针对两个子节点,package和alias子标签进行解析

  • 获取typeAlias的所有子标签
    • 判断是不是package标签,如果是就将其指定包名下面的类全部进行注册,别名就是类名
    • 如果不是package标签,那就默认视为alias标签(注意这里不会去检验是不是alias标签),获取alias标签的alias和type属性,进行注册,假如根据type没找到类,会抛错;假如alias属性为空,那就默认为类名!
解析plugins节点

对应的方法为pluginElement

首先说明一下plugins节点的作用,代表着插件,是MyBatis的扩展机制之一,用户可以通过添加自定义插件在SQL语句执行过程中的某一点进行拦截;而MyBatis中的自定义插件只需要实现Interceptor接口,然后通过注解指定想要拦截的方法签名即可!(后面再介绍使用)

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        //获取plugin节点下面的子标签节点
      for (XNode child : parent.getChildren()) {
          //获取interceptor属性,里面指定了Interceptor接口的实现类
        String interceptor = child.getStringAttribute("interceptor");
          //获取plugins下的properties配置的信息,形成properties对象!
        Properties properties = child.getChildrenAsProperties();
          //使用反射实例化出该拦截插件实例
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
          //拦截插件实例注入配置的properties信息
        interceptorInstance.setProperties(properties);
          //添加进Configuation中!
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  • 遍历plugins节点下面的子标签节点
  • 获取子标签节点的Interceptor属性,获取子标签节点的properties配置信息
  • 通过Interceptor属性去实例出拦截插件的实例
  • 给拦截插件实例注入进properties配置信息
  • 添加进Configuration中,Configuration就会按顺序创建出一条拦截器链,进行一系列的拦截!!
解析objectFactory标签

前面已经提到过ObjectFactory的功能了,并且MyBatis也支持我们自定义ObjectFactory

对应的方法为objectFactoryElement

private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
       //获取objectFactory子标签的type属性
      String type = context.getStringAttribute("type");
      //获取properties配置信息
      Properties properties = context.getChildrenAsProperties();
       //使用type属性指定的类进行反射,创建出ObjectFactory实例
      ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        //给ObjectFactory实例注入配置信息
      factory.setProperties(properties);
        //添加进Configuration中
      configuration.setObjectFactory(factory);
    }
  }

跟plugins节点十分相似,都是获取type属性指定的类型来进行进行实例化创建后,注入properties配置信息,最后添加进Configuration中

解析ObjectWrapperFactory标签

前面已经提到过ObjectWrapperFactory的功能了,并且MyBatis也支持我们自定义ObjectFactory

对应的方法为ObjectWrapperFactoryElement

private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        //获取type属性
      String type = context.getStringAttribute("type");
        //实例化
      ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        //添加进Configuration中
      configuration.setObjectWrapperFactory(factory);
    }
  }

可以看到,解析ObjectWrapperFactory标签跟前面的Plugins节点和ObjectWrapper也是很接近的,唯一的不同点就是没有去解析和注入properties信息

解析reflectorFactory标签

前面已经学过reflectorFactory了,是用来管理Reflector的,也就是数据库类型与Java类型的映射关系

对应的方法为reflectorFactoryElement

private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
        //获取Type属性
      String type = context.getStringAttribute("type");
        //实例化ReflectorFactory
      ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        //注入进Configuration
      configuration.setReflectorFactory(factory);
    }
  }

reflectorFactory标签的解析跟前面的ObjectWrapperFactory标签是相同步骤的,都是直接获取Type属性,然后根据Type属性里面的实现类全限定类名来进行反射创建出实例,然后添加进Configuration中的

对settings剩下的子标签的解析

前面虽然已经对settings标签进行解析了,但仅仅只是注入了settings标签下的vsf和LogImpl子标签,对于其他的子标签都还没有进行解析!这个方法就是对剩下的子标签进行解析和注入进Configuration中的。。。。

在这里插入图片描述

解析environments标签

environments标签,顾名思义就是针对环境的不同的,同一项目可能分为开发、测试和生产多个不同的环境,而每个环境的配置可能也不尽相同,MyBatis可以去配置多个environment节点,并且每个environment节点对应一种环境的配置,尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择其中一个

不过首先要认识environment标签下有什么,主要是两个对象(前面已经看过了)

  • DataSourceFactory:数据源工厂
  • TransactionFactory:事务工厂

对应的方法为environmentsElement

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        //如果没有指定的environment(成员属性)
      if (environment == null) {
          //使用environments节点里面的default属性
        environment = context.getStringAttribute("default");
      }
        //遍历environments标签的子标签,既environment
      for (XNode child : context.getChildren()) {
          //获取environment的id属性
        String id = child.getStringAttribute("id");
          //判断id属性是否与前面指定的environment所匹配
        if (isSpecifiedEnvironment(id)) {
            //如果匹配,创建对应的TransactionFactory
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
            //如果匹配,创建对应的DataSourceFactory
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
            //获取DataSource,既数据源
          DataSource dataSource = dsFactory.getDataSource();
            //使用DataSource与TransactionFactory来创建出environmentBuilder对象
            //这里也用了建造者模式,分开组件进行建造!!!
            //transactionFactory与dataSource
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
            //添加进Configuration中
          configuration.setEnvironment(environmentBuilder.build());
          break;
        }
      }
    }
  }
  • 判空,判断environments子标签的内容是否为空
  • 判断有没有定义要使用的environment,如果没有,就以environments的default属性为准
  • 遍历environments下的所有environment子标签,对应判断environment的id符不符合指定的环境
    • 如果符合,既找到了对应的environment,创建对应的TransactionFactory和DataSourceFactory,然后创建出EnvironmentBuilder,并添加进Configuration中
解析databaseIdProvider标签

MyBatis不像Hibernate一样,可以去屏蔽不同的SQL语言支持方面的差异,但在MyBatis的配置文件中,可以通过databaseProviderId指定该SQL语句引用的数据库产品!!!

实现的原理是:会根据前面已经确定好的DataSource来确定当前使用的数据库产品,Configuration只会存储匹配当前数据库类型的databaseId,然后在解析SQL映射配置文件时,会进行筛选,主要的筛选方式是,只加载不带databaseId的SQL语句和拥有匹配当前数据库的databaseId属性的SQL;反过来就是,带有databseId的,但与当前数据库类型不匹配的,会被舍弃掉;但如果出现带有databaseId和不带databaseId的相同SQL语句,则后者会被舍弃掉!!!!

解析databaseIdProvider标签的方法对应为databaseIdProviderElement

private void databaseIdProviderElement(XNode context) throws Exception {
    //MyBatis使用DatabaseIdProvider来存储databaseIdProvider标签内容
    //MyBatis提供了VendorDatabaseProvider
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        //获取里面的type属性
      String type = context.getStringAttribute("type");
      if ("VENDOR".equals(type)) {
        type = "DB_VENDOR";
      }
        //解析相关的properties配置信息
      Properties properties = context.getChildrenAsProperties();
        //通过指定的type属性对应的实现类,来创建DatabaseIdProvider实例
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
        //注入相关的properties配置信息
      databaseIdProvider.setProperties(properties);
    }
    //获取environment属性(前面已经解析过了)
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        //筛选出匹配的databaseId
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
        //将匹配的databaseId添加进Configuration中
      configuration.setDatabaseId(databaseId);
    }
  }

  • 对于databaseIdProvider,MyBatis使用DatabaseIdProvider对象来进行存储
  • 获取databaseIdProvider里面的Type属性
  • 解析相关的properties配置信息
  • 使用反射,根据Type属性来创建出DatabaseIdProvider实例
  • 从前面解析的environment标签中获取到当前dataSource
  • 选出符合当前dataSource的databaseId
  • 将符合的databaseId添加进Configuration中
解析typeHandlers标签

前面已经看过TypeHandler的作用了,就是用来处理类型映射的,比如java类型与jdbc类型如何对应,并且是通过TypeHandlerRegistry来注册的

 private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        //遍历所有typeHandler子节点
      for (XNode child : parent.getChildren()) {
          //处理package属性
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
            //注册package包名下的TypeHandler
            //(注意:TypeHandler为JdbcType,然后使用MappedType接口映射对应javaType)
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
            //如果不是package属性
            //获取javaType和jdbcType
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
            //获取handler属性
            //handler属性其实跟jdbcType一样
          String handlerTypeName = child.getStringAttribute("handler");
            //使用反射,来创建出javaType和jdbcType对应的类对象
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
                //进行注册
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

typeHandler的解析跟TypeAlias的解析十分类似,不再赘述

解析mapper标签

最后解析的标签就是mapper标签了,用来指定我们的sql映射文件的

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        //获取mapper的子标签
      for (XNode child : parent.getChildren()) {
          //解析package子标签
        if ("package".equals(child.getName())) {
            //获取package子标签的name属性
          String mapperPackage = child.getStringAttribute("name");
            //添加进Configuration中
          configuration.addMappers(mapperPackage);
        } else {
            //如果不是package子标签
            //获取resource属性
          String resource = child.getStringAttribute("resource");
            //获取url属性
          String url = child.getStringAttribute("url");
            //获取class属性
          String mapperClass = child.getStringAttribute("class");
            //resource和url和mapperClass不能同时定义
            //当url、mapperClass不存在,而resource存在时
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
              //使用xmlMapperBuilder来对SQL映射配置文件进行解析
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } 
            //如果resource、mapperClass不存在,而url存在
            else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
                //同样使用XMLMapperBuilder来进行解析SQL映射配置文件
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } 
            //如果resource和url不存在,而mapperClass存在,也就是接口的全限定名
            else if (resource == null && url == null && mapperClass != null) {
                //根据接口的全限定名找到接口的Class对象
            Class<?> mapperInterface = Resources.classForName(mapperClass);
                //添加到Configuration中,将该接口注册进MapperRegistry中
            configuration.addMapper(mapperInterface);
          } else {
                //如果出现重复存在,或者三个都不存在,抛错
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

从代码上可以看到,主要是对mapper标签的两个子标签进行

  • package:对某一包下的接口进行注册
  • mapper:针对url、resource、class属性进行解析和注册(三者只能存在一个)
    • 对于url和resouce都是采用XMLMapperBuilder来进行解析的,所以说url和resouce的值其实是SQL映射文件
    • 对于class则是采用反射创建出接口,然后将其注册进MapperRegistry的,所以说class属性的值是接口的全限定类名

至此,MyBatis的初始化到此结束了,对mybatis-config.xml的总配置文件的解析过程到这里就结束了

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值