欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring集成Mybatis源码分析(三)-配置文件初始化及XML解析

程序员文章站 2022-05-23 16:23:50
...

本节主要讲解Mybatis的XML配置以及Mapper配置

启动入口

首先我们还是看启动入口,一般情况下涉及到远程访问的,都需要拿到Session,mybatis-spring提供了SqlSessionFactoryBean,将该工厂bean注入到容器中,在Spring启动过程中会去调用该类的的afterPropertiesSet方法和getObject方法获取session

@Autowired
@Value("classpath*:com/test/mapper/**/*.xml")
private Resource[] mapperLocations;

@Bean
public SqlSessionFactoryBean sessionFactoryBean(DataSource dataSource) {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(multipleDataSource);
    sessionFactory.setMapperLocations(mapperLocations);
    return sessionFactory;
}

下面来看SqlSessionFactoryBean的afterPropertiesSet方法,这个方法在属性注入后才会执行,该方法直接调用了buildSqlSessionFactory方法

public void afterPropertiesSet() throws Exception {
    // 校验空
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

    this.sqlSessionFactory = buildSqlSessionFactory();
}

buildSqlSessionFactory方法比较长, 大致做了以下这些事,后面我们一个一个分析

  1. 创建mybatis配置对象,如果指定了XML配置文件,则利用XMLConfigBuilder对象创建Configuration对象,如果没有指定XML配置文件,那么久创建一个默认的Configuration对象
  2. 设置创建实例对象的工厂,可以决定如何创建对象
  3. 设置实例包装的工厂,可以通过该Factory改变返回实例的值等
  4. 注册别名
  5. 添加插件
  6. 添加类型转换器
  7. 解析Mybatis XML配置文件
  8. 添加事务工厂
  9. 设置数据库ID
  10. 解析Mapper XML

主流程分析

  • 创建XMLConfigBuilder以获取Configuration对象或新建Configuration对象
    • 在该类中创建一个Configuration对象
    • 创建一个XPathParser对象,用来后续解析XML文件
    • 传入new SqlSessionFactoryBean时指定的Properties
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;
  }
  • 设置对象工厂和对象包装工厂
if (this.objectFactory != null) {
    configuration.setObjectFactory(this.objectFactory);
}

if (this.objectWrapperFactory != null) {
    configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
  • 如果不指定,默认为DefaultObjectFactory,DefaultObjectWrapperFactory
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  • 别名注册
if (hasLength(this.typeAliasesPackage)) {
    String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                                                           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                                                             typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (logger.isDebugEnabled()) {
            logger.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
    }
}

if (!isEmpty(this.typeAliases)) {
    for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (logger.isDebugEnabled()) {
            logger.debug("Registered type alias: '" + typeAlias + "'");
        }
    }
}
  • 包名别名指定字符串,用,; \t\n分隔,这些包下的接口都会注册别名
  • 接口别名默认注册未类的接口名,也可以在接口上加@Alias指定别名
  • 默认采用TypeAliasRegistry注册器注册别名,在实例化Configuration时默认注册了以下别名
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
  • 添加插件
    • 添加实现了Interceptor接口的插件
    • 添加到Configuration属性的InterceptorChain下
if (!isEmpty(this.plugins)) {
    for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (logger.isDebugEnabled()) {
            logger.debug("Registered plugin: '" + plugin + "'");
        }
    }
}
  • 添加类型处理器
    • 同别名注册器查不到,提供了包或者执行接口的类型处理器
if (hasLength(this.typeHandlersPackage)) {
    String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                                                              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (logger.isDebugEnabled()) {
            logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
    }
}

if (!isEmpty(this.typeHandlers)) {
    for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (logger.isDebugEnabled()) {
            logger.debug("Registered type handler: '" + typeHandler + "'");
        }
    }
}
  • 默认实现类是protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();默认实现了如下类型处理器
  • 实现自定义的TypeHandler需要实现该接口,并加上注解@MappedTypes用来表明哪些类型需要使用该TypeHandler转换,或者继承TypeReference通过泛型自动识别类型转换
public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }
  • 解析XML配置
    • 如果指定了Mybatis的配置文件,那么就需要解析
if (xmlConfigBuilder != null) {
    try {
        // 如果
        xmlConfigBuilder.parse();

        if (logger.isDebugEnabled()) {
            logger.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
    } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
        ErrorContext.instance().reset();
    }
}
  • 解析的主要流程和上面提到的流程差不多,主要读取各个节点的信息,然后将各种信息赋值到Configuration对象上
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 加载配置文件,本地resource或者远程url
      propertiesElement(root.evalNode("properties"));
      // 别名
      typeAliasesElement(root.evalNode("typeAliases"));
        // 插件
      pluginElement(root.evalNode("plugins"));
        // 对象实例化及包装工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 反射工厂
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
        // 设置选项
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
        // 环境
      environmentsElement(root.evalNode("environments"));
        // 数据库ID提供者
      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);
    }
  }
  • 其中settings的配置包括如下, 也就是把设置的setting中的属性赋值给Configuration对象
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  • 创建事务工厂
    • 没有指定则使用Mybatis实现的SpringManagedTransactionFactory
if (this.transactionFactory == null) {
    this.transactionFactory = new SpringManagedTransactionFactory();
}
  • 为当前Configuration设置DatabaseId
    • 当我们有多个数据源的时候我们可以new 多个SqlSessionFactoryBean,并为其指定DatabaseIdProvider,该provider可以返回一个String类型的databaseId,可以实现数据库分离
if (this.databaseIdProvider != null) {
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }
  • 解析Mapper XML文件
    • 本人认为这个解析是整个XML解析中最重要的,另外,不知道大家有没有注意到,在之前的xmlConfigBuilder.parse();里面也有mapperElement(root.evalNode(“mappers”));方法,这个方式最终的逻辑其实和这里一直,即新建XMLMapperBuilder,然后调用它的parse方法,一般在使用mabatis和Spring集成时,我比较倾向与使用sqlSessionFactoryBean.setMapperLocations方法指定Mapper Xml的位置,而mybatis的XML一般很少使用
    • 此处会遍历所有mapperLocations位置下的文件
if (!isEmpty(this.mapperLocations)) {
    
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (logger.isDebugEnabled()) {
          logger.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
  • 那么我们现在来看下这个XMLMapperBuilder的parse方法
public void parse() {
    //首先判断当前资源是否已经被加载过,主要时根据resource的全路径名称是否集合中来判断
    if (!configuration.isResourceLoaded(resource)) {
        //然后开始解析mapper节点
        configurationElement(parser.evalNode("/mapper"));
        //标识为已解析过
        configuration.addLoadedResource(resource);
        //绑定命名空间
        bindMapperForNamespace();
    }
	
    // 转换未完成的ResultMap
    parsePendingResultMaps();
    // 转换之前未处理成功的缓存引用
    parsePendingChacheRefs();
    parsePendingStatements();
}
  - 继续分析configurationElement(parser.evalNode("/mapper"))方法
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. Cause: " + e, e);
    }
}
     - cacheRefElement:解析缓存引用
     - cacheElement:解析缓存配置
        - 第一步:读取<cache>上的属性
private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      	// 缓存的类型,默认PerpetualCache
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // 剔除策略,默认LruCache
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        // 刷新间隔
      Long flushInterval = context.getLongAttribute("flushInterval");
        // 缓存数量
      Integer size = context.getIntAttribute("size");
        // 可读可写
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        // 阻塞
      boolean blocking = context.getBooleanAttribute("blocking", false);
        // 其他属性
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
        - 第二步:然后调用useNewCache方法新建Cache并添加一个Cache到Configuration,具体做了哪些动作参考下面的代码
public Cache useNewCache(Class<? extends Cache> typeClass,
                         Class<? extends Cache> evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    typeClass = valueOrDefault(typeClass, PerpetualCache.class);
    evictionClass = valueOrDefault(evictionClass, LruCache.class);
    // 建造者模式
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(typeClass)
        .addDecorator(evictionClass)
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}
// 以下代码未CacheBuilder的build方法
public Cache build() {
    setDefaultImplementations();
    // id为当前命名空间即currentNamespace,默认新建了PerpetualCache
    Cache cache = newBaseCacheInstance(implementation, id);
    // 为某些Cache实现设置属性
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    // 为PerpetualCache添加装饰器模式,如默认的LruCache,他有一个参数为Cache的构造器,将PerpetualCache传进去
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      // 继续装饰缓存,我把这段代码copy到这里
      /** 
      		try {
              MetaObject metaCache = SystemMetaObject.forObject(cache);
              // 根据<cache>中的size设置能缓存的条数
              if (size != null && metaCache.hasSetter("size")) {
                metaCache.setValue("size", size);
              }
              // 如果clearInterval不为空,装饰一个ScheduledCache
              if (clearInterval != null) {
                cache = new ScheduledCache(cache);
                ((ScheduledCache) cache).setClearInterval(clearInterval);
              }
              // 装饰SerializedCache,写缓存时序列化后的字节数组,读缓存从字节码中反序列化
              if (readWrite) {
                cache = new SerializedCache(cache);
              }
              // 装饰日志功能,dubug级别下可打印缓存命中率
              cache = new LoggingCache(cache);
              // 装饰同步功能,读写都加了synchonized,读写同步
              cache = new SynchronizedCache(cache);
              // 装饰阻塞功能,获取缓存的时候不允许写入,锁粒度为namespace + 方法名 + 参数值
              if (blocking) {
                cache = new BlockingCache(cache);
              }
              return cache;
            } catch (Exception e) {
              throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
            }
    	*/
      cache = setStandardDecorators(cache);
        // 如果不是PerpetualCache,也不是LoggingCache,只装饰一个LoggingCache
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
     - parameterMapElement:解析参数配置
        - 解析<parameterMap>节点
private void parameterMapElement(List<XNode> list) throws Exception {
    for (XNode parameterMapNode : list) {
        String id = parameterMapNode.getStringAttribute("id");
        String type = parameterMapNode.getStringAttribute("type");
        // java类
        Class<?> parameterClass = resolveClass(type);
        List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
        List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
        // 获取各种参数映射
        for (XNode parameterNode : parameterNodes) {
            String property = parameterNode.getStringAttribute("property");
            String javaType = parameterNode.getStringAttribute("javaType");
            String jdbcType = parameterNode.getStringAttribute("jdbcType");
            String resultMap = parameterNode.getStringAttribute("resultMap");
            String mode = parameterNode.getStringAttribute("mode");
            String typeHandler = parameterNode.getStringAttribute("typeHandler");
            Integer numericScale = parameterNode.getIntAttribute("numericScale");
            // 参数类型,IN/OUT/INOUT
            ParameterMode modeEnum = resolveParameterMode(mode);
            // java参数类型
            Class<?> javaTypeClass = resolveClass(javaType);
            // jdbcType类型
            JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
            @SuppressWarnings("unchecked")
            // 类型处理器
            Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
            ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
            parameterMappings.add(parameterMapping);
        }
        // 将上面得出的所有parameterMapping加入到configuration中的map中,以currentNamespace + "." + <parameterMap>的id属性作为key
        builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
}

// 该方法在MapperBuilderAssistant中
public ParameterMapping buildParameterMapping(
    Class<?> parameterType,
    String property,
    Class<?> javaType,
    JdbcType jdbcType,
    String resultMap,
    ParameterMode parameterMode,
    Class<? extends TypeHandler<?>> typeHandler,
    Integer numericScale) {
    
    // 指明resultMap的命名空间
    resultMap = applyCurrentNamespace(resultMap, true);

    // Class parameterType = parameterMapBuilder.type();
    // 如果javaType指定了,返回javaType,否则推断javaType
    // 如果是JdbcType.CURSOR,那么jdbcType=ResultSet
    // 否则如果<parameterMap>指定的type为Map,jabcType=Object
    // 否则根据type和property获取get方法的返回类型
    // 否则为Object
    Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
    // 从初始化的TypeHandlerRegistry中获取类型处理器或者自定义类型处理器
    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

    // 又是建造者模式,返回一个ParameterMapping实例
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, javaTypeClass);
    builder.jdbcType(jdbcType);
    builder.resultMapId(resultMap);
    builder.mode(parameterMode);
    builder.numericScale(numericScale);
    builder.typeHandler(typeHandlerInstance);
    return builder.build();
}
     - resultMapElements:解析结果集配置
        - 流程基本与ParameterMapping差不多,但是这里有一个地方有区别,即extends,resultMap可以继承自另一个resultMap,而由于解析是根据XML文件由上至下一个一个解析,因此如果前面解析出来的ResultMap是依赖于其后面的ResultMap,那么此时它就无法得到完整的ResultMap,因此需要等整个文件解析完成后再来处理这种继承关系。
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取到id
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    // 获取到type
    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<ResultMapping>();
    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<ResultFlag>();
        // 如果是<id>节点,打上标识
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        // 构建resultMapping,这里就是采用MapperBuilderAssistant的buildResultMapping方法根据节点上的属性,构建一个resultMapping对象
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 创建一个ResultMap解决器,如果本次失败,捕获异常,会丢到未完成的ResultMap中,后续处理
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
        // 解析完成后添加到Configuration中,key和parmeterMap的格式一样
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
     - sqlElement:解析sql片段
        - 这段比较简单,就是把整个<sql>添加到sqlFragments中,后续在buildStatement时如果有引用该sql,那么把它的内容拼接进去
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        sqlFragments.put(id, context);
      }
    }
  }
     - buildStatementFromContext:解析各种增删改查
        - 从这个方法跟进来
public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // 将include替换成sqlFragments中的内容,主要逻辑是遍历判断当前节点,如果是include,就去sqlFagments中查找对应的,
    // 如果不是,递归判断子节点,直到所有节点都是Attribute节点或者是Text节点为止
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // 解析selectKey,会生成一个单独的statement存储在Configuration中,key设置为引入<selectKey>的标签的namespace + "." + id + "!selectKey"
    // 同时生成一个SelectKeyGenerator的对象存储在Configuration中
    // 最后把<selectKey>节点删除
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // 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);
    // 如果在节点下定义了selectKey,那么就是使用自己定义的,如果没有定义,并且mybatis配置文件中配置了useGeneratedKeys,并且当前节点为INSERT
    // 那么就是用Jdbc3KeyGenerator,否则为NoKeyGenerator,即不使用键生成器
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    // 根据各种配置为statement绑定cache,paremeterMap, resultMap以及各种属性
    // 最后添加到Configuration的map属性中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
  - 到这类XML格式定义的Mapper就已经解析完成了,我们回到XMLMapperBuilder的parse方法,再来继续看bindMapperForNamespace方法
     - 根据XML中定义的namespace,找到对应的接口,然后调用configuration.addMapper方法
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);
        }
      }
    }
  }
     - 然后调用到了mapperRegistry的addMapper方法
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 {
            // 定义一个MapperProxyFactory,这个mapperProxyFactory可以创建出MapperProxy,及JDK动态代理对象
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            // 然后开始解析接口中注解
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}
     - parse方法
public void parse() {
    String resource = type.toString();
    // 判断接口是否解析过,如果是从前面的流程一路走下来的,这里肯定不会被加载过,而下面的loadXmlResource()方法中的!configuration.isResourceLoaded("namespace:" + type.getName()这个判断就一定会返回false,
    // 因为我们在bindMapperForNamespace方法中调用了configuration.addLoadedResource("namespace:" + namespace);方法,因此不会再去加载XML文件了
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
        // 缓存注解解析@CacheNamespace @CacheNamespaceRef,这里不会出现XML哪里的未完成状态,因为是直接指定的对象,不存在解析先后顺序的问题
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
              // 这里的解析和XML差不多,只不过是把XML中的配置搬到了注解中
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }
     - parseStatement
void parseStatement(Method method) {
    // 找到参数类型,如果不是RowBound和ResultHandler,如果只有一个参数,就返回该参数的类型,如果是多个就返回ParamMap
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 从方法上读取Sql
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
        // 读取可选配置
      Options options = method.getAnnotation(Options.class);
        // 设置默认值
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        // 非select语句默认刷新缓存
      boolean flushCache = !isSelect;
        // select语句使用缓存
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
        // 如果是insert或者update语句,读取SelectKey注解
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
          // 如果标注有SlectKey注解,读取该注解,然后和XML一样注册一个MappedStatement,和一个SelectKeyGenerator
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
            // 如果mybatis配置了useGenerateKeys,默认使用Jdbc3KeyGenerator,否则不适用KeyGenerator
          keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        } else {
            // 如果可选配置上配置了useGeneratedKeys,同上
          keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        keyGenerator = new NoKeyGenerator();
      }

        // 可选配置覆盖默认配置
      if (options != null) {
        flushCache = options.flushCache();
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

        // 构建resultMapId
      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

        // 添加MappedStatement
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          null);
    }
  }
  • 最后把之前缓存的未完成的方法,再调一次resolve方法,流程和之前解析的时候一样,因为这时整个XML中所有的resultMap,CacheRef,都已经被正确解析了,XML前面出错的就可以引用到后面的了。
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
  • 最后创建一个SqlSessionFactory的实例赋值给SqlSessionFactoryBean的sqlSessionFactory属性,后续在调用FactoryBean的getObject方法时就会返回该对象。
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

此时最终在Configuration对象中会产生如下部分重要的属性,

  1. interceptorChain:拦截器链
  2. mappedStatements:已解析好的Sql表达式
  3. caches:配置过的缓存
  4. resultMaps: 结果映射集
  5. parameterMaps:参数映射
  6. keyGenerator:主键生成器
  7. sqlFragments:sql片段
  8. cacheRefMap:缓存引用

Spring集成Mybatis源码分析(三)-配置文件初始化及XML解析