Mybatis源码学习第五课---核心层源码分析--配置加载阶段

本文详细介绍了MyBatis初始化过程,包括mybatis-config.xml配置文件和映射器配置文件的解析。核心类Configuration、MapperRegistry、ResultMap和MappedStatement的解析过程被详细阐述,解析过程涉及XMLConfigBuilder、XMLMapperBuilder和XMLStatementBuilder。文章通过源码分析揭示了MyBatis如何利用建造者模式构建Configuration对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

一、 mybatis初始化

        类似于 Spring、 MyBatis 等灵活性和可扩展性都很高的开源框架都提供了很多配置项,开发人员需要在使用时提供相应的配置信息,实现相应的需求。

        MyBatis 中的配置文件主要有两 个,分别是 mybatis-config.xml 配置文件和映射配置文件。 现在主流的配置方式除了使用 XML 配置文件,还会配合注解进行配置。在 MyBatis 初始 化的过程中,除了会读取 mybatis-config.xml 配置文件以及映射配置文件,还会加载配直文件指 定的类,处理类中的注解,创建一些配置对象,最终完成框架中各个模块的初始化。另外,也 可以使用 Java API 的方式对 MyBatis 进行配置,这种硬编码的配置方式主要用在配置量比较少 且配置信息不常变化的场景下。      

         

1.1mybatis初始化

   过程主要使用建造者模型

   

  • XMLConfigBuilder:主要负责解释mybatis-config.xml
  • XMLMapperBuilder:负责解析映射配置文件
  • XMLStatementBuilder:负责解析映射配置文件中的SQL结点

1.2 映射器的关键类

  • Configuration:Mybatis启动初始化的核心就是将所有xml配置文件信息加载到Configuration对象中,Configuration是单例的,生命周期是应用级的
  • MapperRegistry:mapper接口动态代理工厂类的注册中心。在Mybatis中,通过mapperProxy实现InvocationHandler接口,MapperProxyFactory用于生成动态代理的实例对象
  • ResultMap:用于解析mapper.xml文件中的resultMap节点,使用ResultMapping来封装id、result等子元素
  • MappedStatement:用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包含了这些节点很多重要属性
  • SqlSource:mapper.xml文件中sql语句会被解析成SqlSource对象,经过解析SqlSource包含的语句最终仅仅包含?占位符,可以直接交给数据库执行。

1.3 configuration类图解

1.4 ResultMap图解

       

1.5 mappedStatment图解

二 、源码解析

2.1 Configuration的域对应mybatis-config.xml中相应的配置项

       

     

public class Configuration {

  protected Environment environment;

  /* 是否启用数据组A_column自动映射到Java类中的驼峰命名的属性**/
  protected boolean mapUnderscoreToCamelCase;
  
  /*当对象使用延迟加载时 属性的加载取决于能被引用到的那些延迟属性,否则,按需加载(需要的是时候才去加载)**/
  protected boolean aggressiveLazyLoading;
  
  /*是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true **/
  protected boolean multipleResultSetsEnabled = true;
  
  /*-允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false**/
  protected boolean useGeneratedKeys;
   
  /*配置全局性的cache开关,默认为true**/
  protected boolean cacheEnabled = true;

  /*指定 MyBatis 应如何自动映射列到字段或属性*/
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;

  /*MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO*/
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  /*延迟加载的全局开关*/
  protected boolean lazyLoadingEnabled = false;
  
  /*指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具*/
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  /*插件集合*/
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  
  /*TypeHandler注册中心*/
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  
  /*TypeAlias注册中心*/
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  //-------------------------------------------------------------

  /*mapper接口的动态代理注册中心*/
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  /*mapper文件中增删改查操作的注册中心*/
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
  
  /*mapper文件中配置的所有resultMap对象  key为命名空间+ID*/
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  
  /*加载到的所有*mapper.xml文件*/
  protected final Set<String> loadedResources = new HashSet<>();
  

2.2 BaseBuilder  

    MyBatis 初始化的主 要工作是加载井解析 mybatis-config.xml 配置文件、映射配置文件以及相关的注解信息。 MyBatis 的初始化入口是 SqlSessionFactoryBuilder. build()方法,其具体实现如下:

 // 读取mybatis配置文件
            inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            //1. SqlSessionFactoryBuilder创建 SqlSessionFactory
            /**
             * 读取配置信息创建SqlSessionFactory,建造者模式,方法级别生命周期;
             * SqlSessionFactory:创建Sqlsession,工厂单例模式,存在于程序的整个生命周期;
             */
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);


 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      ////读取配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //解析配置文件得到 Configuration 对象,创建 DefaultSqlSessionFactory 对象
      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.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

        SqlSessionFactoryBuilder.build()方法会创建 XMLConfigBuilder 对象来解析 mybatis-config 配置文件,而 XMLConfigBuilder 继承自 BaseBuilder 抽象类, 

      正如前面所说, MyBatis 的初始化过程使用了建造者模式,这里的 BaseBuilder 抽象类就扮 演着建造者接口 的角色。 BaseBuilder 中核心宇段的含义如下:

  // Configuration 是 MyBatis 初始化过程的核心对象,
  // MyBatis 中几乎全部的配置信息会保存到 Configuration对象中。
  // Configuration 对象是在 MyBatis 初始化过程中创建且是全局唯一的,
  // 也有人称它是一个“All-In-One”配置对象
  protected final Configuration configuration;
  //在 mybatis-config.xml配置文件中可以使用<typeAliases>标签定义别名,这些定义的别名都会记录在该
  protected final TypeAliasRegistry typeAliasRegistry;
  //在 mybatis-config.xml 配置文件中可以使用<typeHandlers>标签添加自定义 TypeHandler器,完
  // 成指定数据库类型与 Java 类型的转换,这些 TypeHandler 都会记录在 TypeHandlerRegistry 中。
  protected final TypeHandlerRegistry typeHandlerRegistry;

2.3 XMLConfigBuilder解析mybatis-config.xml,将解析出的相关的值加入到Configuration对象中

   XMLConfigBuilder 是 BaseBuilder 的众多子类之一,它扮演的是具体建造者的角色。 XMLConfigBuilder 主要负责解析 mybatis-config.xml 配置文件,其核心字段如下:

  //标识是否已经解析过 mybatis-config.xml 配置丈件
  private boolean parsed;
  //用于解析 mybatis-config.xml 配置文件的 XPathParser 对象
  private final XPathParser parser;
  //标识<environment>配置的名称,默认读取<env工ronment>标签的 default 属性
  private String environment;
  /// ReflectorFactory 负责创建和缓存 Reflector 对象
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

          XMLConfigBuilder.parse()方法是解析 mybatis-config且nl 配置文件的入口,它通过调用 XMLConfigBuilder.parseConfiguration()方法实现整个解析过程, 具体实现如下:

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //在 mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //解析<properties>节点
      propertiesElement(root.evalNode("properties"));
      //解析<settings>节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      //解析<typeAliases>节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析<plugins>节点
      pluginElement(root.evalNode("plugins"));
      //解析<objectFactory>节点
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析<objectWrapperFactory>节点
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析<reflectorFactory>节点
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);//将settings填充到configuration
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析<environments>节点
      environmentsElement(root.evalNode("environments"));
      //解析<databaseIdProvider>节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析<typeHandlers>节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

2.4 XMLMapperBuilder解析mapper.xml映射文件

config中配置的xml文件示例如下:

    <!-- 映射文件,mapper的配置文件 -->
    <mappers>
        <!--直接映射到相应的mapper文件 -->
        <mapper resource="sqlmapper/TUserMapper.xml" />
        <mapper resource="sqlmapper/TJobHistoryMapper.xml" />
        <mapper resource="sqlmapper/TPositionMapper.xml" />
        <mapper resource="sqlmapper/THealthReportFemaleMapper.xml" />
        <mapper resource="sqlmapper/THealthReportMaleMapper.xml" />
         <mapper class="com.enjoylearning.mybatis.mapper.TJobHistoryAnnoMapper"/>
    </mappers>

通过对 XMLConfigBuilder.mapperElement()方法用来解析上面文件:

//解析<mappers>节点
 mapperElement(root.evalNode("mappers"));
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {//处理mapper子节点
        if ("package".equals(child.getName())) {//package子节点
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {//获取<mapper>节点的resource、url或mClass属性这三个属性互斥
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {//如果resource不为空
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);//加载mapper文件
            //实例化XMLMapperBuilder解析mapper映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {//如果url不为空
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);//加载mapper文件
            //实例化XMLMapperBuilder解析mapper映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {//如果class不为空
            Class<?> mapperInterface = Resources.classForName(mapperClass);//加载class对象
            configuration.addMapper(mapperInterface);//向代理中心注册mapper
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

XMLMapperBuilder 负责 解析映射配置文件,它继承了 BaseBuilder 抽象类,也是具体建造者的角色。 XMLMapperBuilder.parse()方法是解析映射文件的入口 , 具体代码如下:

 public void parse() {
    //判断是否已经加载该配置文件
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));//处理mapper节点
      configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
      bindMapperForNamespace();//注册mapper接口
    }
    //处理解析失败的ResultMap节点
    parsePendingResultMaps();
    //处理解析失败的CacheRef节点
    parsePendingCacheRefs();
    //处理解析失败的Sql语句节点
    parsePendingStatements();
  }

 

<mapper namespace="com.enjoylearning.mybatis.mapper.TUserMapper">

    <cache></cache>
    <resultMap id="BaseResultMap" type="TUser">

        <!-- <constructor> <idArg column="id" javaType="int"/> <arg column="user_name"
            javaType="String"/> </constructor> -->
        <id column="id" property="id" />
        <result column="user_name" property="userName" />
        <result column="real_name" property="realName" />
        <result column="sex" property="sex" />
        <result column="mobile" property="mobile" />
        <result column="email" property="email" />
        <result column="note" property="note" />
    </resultMap>

    <resultMap id="userAndPosition1" extends="BaseResultMap" type="TUser">
        <association property="position" javaType="TPosition" columnPrefix="post_">
            <id column="id" property="id"/>
            <result column="name" property="postName"/>
            <result column="note" property="note"/>
        </association>
    </resultMap>

    <resultMap id="userAndPosition2" extends="BaseResultMap" type="TUser">
        <!--<association property="position"  column="position_id" select="com.enjoylearning.mybatis.mapper.TPositionMapper.selectByPrimaryKey" />-->
        <association property="position" fetchType="lazy"  column="position_id" select="com.enjoylearning.mybatis.mapper.TPositionMapper.selectByPrimaryKey" />
    </resultMap>

    <select id="selectUserPosition1" resultMap="userAndPosition1">
        select a.id,user_name,real_name, sex, mobile,email, a.note, b.id  post_id,
            b.post_name, b.note post_note from t_user a,t_position b where a.position_id = b.id

   </select>

 

XMLMapperBuilder 也是将每个节点 的解析过程封装成了一个方法,而这些方法由XMLMapperBuilder.configurationElement()方法调用, 本小节将逐一分析这些节点的解析过程, configurationElement()方法的具体实现如下:

 private void configurationElement(XNode context) {
    try {
      //获取mapper的xml文件中的namespace节点
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //设置builderAssistant的namespace属性
      builderAssistant.setCurrentNamespace(namespace);
      //解析<cache-ref> 节点
      cacheRefElement(context.evalNode("cache-ref"));
      //解析<cache>节点
      cacheElement(context.evalNode("cache"));
      //解析<parameterMap>节点(该节点 已废弃,不再推荐使用,不做详细介绍)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //解析<resultMap>节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //解析<sql>节点
      sqlElement(context.evalNodes("/mapper/sql"));
      //解析<select>、<insert>、<update>、<delete>等 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);
    }
  }

   2.4.1 解析<cache>节点

       MyBatis 拥有非常强大的二级缓存功能, 该功能可以非常方便地进行配置, MyBatis 默认情 况下没有开启二级缓存,如果要为某命名空间开启二级缓存功能,则需要在相应映射配置文件 中添加<cache>节点,还可以通过配置<cache>节点的相关属性,为二级缓存配置相应的特性 (本 质上就是添加相应的装饰器〉。 XMLMapperBuilder.cacheElement()方法主要负责解析<cache>节点, 其具体实现如下:

private void cacheElement(XNode context) {
    if (context != null) {
      //获取<cache>节点的 type的属性,默认位是 PERPETUAL
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      //获取<cache>节点的 eviction属性,也就是缓存的淘汰策略,默认LRU(最近最少使用淘汰策略)
      String eviction = context.getStringAttribute("eviction", "LRU");
      //根据eviction属性,找到装饰器
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      //读取flushInterval属性,既缓存的刷新周期,,默认值是 null
      Long flushInterval = context.getLongAttribute("flushInterval");
      //读取size属性,既缓存的容量大小
      Integer size = context.getIntAttribute("size");
      //读取readOnly属性,既缓存的是否只读,默认位是 false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      //读取blocking属性,既缓存的是否阻塞 默认位是 false
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 获取<cache>节点下的子节点,将用于初始化二级缓存
      Properties props = context.getChildrenAsProperties();
      //通过builderAssistant创建缓存对象,并添加至configuration
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

经典的建造起模式,创建一个cache对象   MapperBuilderAssistant.useNewCache()方法的实现:

//创建相关的cache 对象
  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    //创建 Cache 对象,这里使用了建造者模式, CacheBuilder 是建造者的角色,而 Cache 是生成的产品
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

     CacheBuilder 是 Cache 的建造者, 这里重点分析 CacheBuilder.build()方法, 该方法根据 CacheBuilder 中上述字 段的值创建 Cache 对象并添加合适的装饰器, 具体实现如下:

  public Cache build() {
    //implementation 字段和 decorators 集合为空,则为其设立默认佳, implementation 默认
    // 位是 PerpetualCache.class, decorators 集合,默认只包含 LruCache.class
    setDefaultImplementations();
    //根据 implementation 指定的类型 , 通过反射获取参数为 String 类型的构造方法,并通过该构造方
    // 法创建 Cache 对象
    Cache cache = newBaseCacheInstance(implementation, id);
    //根据<cache>节点下自己置的<property>信息,初始化 Cache 对象
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    //检测 cache 对象的类型,如果是 PerpetualCache 类型,为其添加 decorators 集合中
    // 的装饰器; 如采是自定义类型的 Cache 接口 实现,则不添加 decorators 集合中的装饰器
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        //通过反射获取参数为 Cache 类型的构造方法,并通过该构造方法创建装饰器
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      //添加 MyBatis 中提供的标准装饰器
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

  CacheBuilder.setStandardDecorators()方法会根据 CacheBuilder 中各个字段的值,为 cache 对 象添加对应的装饰器,具体实现如下

 //缓存模块装饰器的使用
  private Cache setStandardDecorators(Cache cache) {
    try {
      //获取基本的cache对象
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      //缓存的容量大小
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      //检测是否指定了 clearinterval 字段 (定期清除)
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);//添加定期清除的装饰器
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      //如果是只读缓存
      if (readWrite) {
        //是否只读,对应添加 SerializedCache 装饰器
          cache = new SerializedCache(cache);
      }
      //默认添加 LoggingCache 和 SynchronizedCache 两个装饰器
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {//阻塞装饰器
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

2.4.2 解析resultMap

    select 语句查询得到的结果集是一张二维表,水平方向上看是一个个字段,垂直方向上看是 一条条记录。 而 Java 是面向对象的程序设计语言,对象是根据类定义创建的,类之间的引用关 系可以认为是嵌套的结构。在 JDBC 编程中,为了将结果集中的数据映射成对象,我们需要自 己写代码从结果集中获取数据,然后封装成对应的对象并设置对象之间的关系,而这些都是大 量的重复性代码。为了减少这些重复的代码, MyBatis 使用<resultMap>节点定义了结果集与结 果对象 OavaBean 对象〉之间的映射规则,<resultMap>节点可以满足绝大部分的映射需求,从 而减少开发人员的重复性劳动,提高开发效率。 

public class Configuration {
  /*mapper文件中配置的所有resultMap对象  key为命名空间+ID*/
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");

       ResultMapping

     每个 ResultMapping 对象记录了结果集中的一列与 JavaBean 中一个属性之间的映射关系。在后面的 分析过程中我们可以看到,<resultMap>节点下除了<discriminator>子节点的其他子节点,都会 被解析成对应的 ResultMapping 对象。 ResultMapping 中的核心字段含义如下:

 //configuration对象
  private Configuration configuration;
  private String property;//对应节点的 column 属性,表示的是从数据库中得到的列名或是列名的别名
  private String column;//对应节点的 property 属性,表示的是与该列进行映射的属性
  private Class<?> javaType;//对应节点的 javaType 属性,表示的是一个 JavaBea口的完全限定名,或一个类型别名
  private JdbcType jdbcType;//对应节点的jdbcType 属性,表示的是进行映射的列的 JDBC 类型
  //对应节点的 typeHandler 属性,表示的是类型处理器,它会覆盖默认的类型处理器,后面会介绍该字段的作用
  private TypeHandler<?> typeHandler;
  //对应节点的 resultMap 属性,该属性通过id引用了另一个<resultMap>节点定义,它负 责将结采集中的一部
  //分列映射成其他关联的结果对象。 这样我们就可以通过 join方式进行关联查询,然后直接映射成多个对象,
  //并同时设置这些对象之间的组合关系
  private String nestedResultMapId;

  private String nestedQueryId;
  private Set<String> notNullColumns;
  private String columnPrefix;//对应节点的 columnPrefix 属性
  private List<ResultFlag> flags;///处理后的标志,标志共两个: id 和 constructor
  private List<ResultMapping> composites;
  private String resultSet;//对应节点的 resultSet 属性
  private String foreignColumn;
  private boolean lazy;///是否延迟加载,对应节点的 fetchType 属性

     ResultMap

     ResultMap, 每个<resultMap>节点都会被解析成一个 ResultMap 对象,其中每个节点所定义的映射关系,则使用 ResultMapping 对象表示,如下图。

   

ResultMap 中各个字段的含义如下:

public class ResultMap {
  private Configuration configuration;//configuration对象

  private String id;//resultMap的id属性
  private Class<?> type;//resultMap的type属性
  private List<ResultMapping> resultMappings;//除discriminator节点之外的映射关系
  private List<ResultMapping> idResultMappings;//记录ID或者<constructor>中idArg的映射关系
  private List<ResultMapping> constructorResultMappings;记录<constructor>标志的映射关系
  private List<ResultMapping> propertyResultMappings;//记录非<constructor>标志的映射关系
  private Set<String> mappedColumns;//记录所有有映射关系的columns字段
  private Set<String> mappedProperties;//记录所有有映射关系的property字段
  private Discriminator discriminator;//鉴别器,对应discriminator节点
  private boolean hasNestedResultMaps;//是否有嵌套结果映射
  private boolean hasNestedQueries;是否有嵌套查询
  private Boolean autoMapping;//是否开启了自动映射

2.5 XMLStatementBuilder解析SQL语句

    映射配置文件中还有一类比较重要的节点需要解析,也就是本节 将要介绍的 SQL 节点。这些 SQL 节点主要用于定义 SQL 语句,它们不再由 XMLMapperBuilder 进行解析,而是由 XMLStatementBuilder 负责进行解析。

      //重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

  //解析select、insert、update、delete节点
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  //处理所有的sql语句节点并注册至configuration对象
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      //创建XMLStatementBuilder 专门用于解析sql语句节点
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        //解析sql语句节点
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

 增删改查sql语句对应的对象MappedStatement:

   

三 绑定 Mapper 接口

       每个映射配置文件的命名空间可以绑定一个 Mapper 接口,井注册到 MapperRegis盯f 中。 MapperRegist:ry 以及其他相关类的实现在分析 binding 模块时介绍,这里不再重复。在 XMLMapperBuilder. bindMapperForNamespace()方法中,完成了映射配置文件与对应 Mapper 接 口的绑定,具体实现如下:

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)) {
          //追加 namespace 前缀,并添加到 Configuration.loadedResources 集合中保存
          configuration.addLoadedResource("namespace:" + namespace);
          //调用 MapperRegistry .addMapper ()方法,注册 boundType 接口
          configuration.addMapper(boundType);
        }
      }
    }
  }

四 总结Configuration建造过程的总结

XMLConfigBuilder,XMLMapperBuilder和XMLStatementBuilder整体层次上并没有采用建造者的流式编程风格,但是采用了建造者模式的灵魂,因为这三个建造者分别构建了Configuration对象的不同部分。

MapperBuilderAssistant builderAssistant调用经典的建造者模式

 //通过builderAssistant创建缓存对象,并添加至configuration
  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    //经典的建造起模式,创建一个cache对象
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值