前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis启动之XMLConfigBuilder解析配置文件(二)

MyBatis启动之XMLConfigBuilder解析配置文件(二)

作者头像
ytao
发布2020-06-04 11:07:35
9820
发布2020-06-04 11:07:35
举报
文章被收录于专栏:ytao

前言

XMLConfigBuilderBaseBuilder(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到 Configuration中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。

使用

调用 XMLConfigBuilder进行解析,要进行两步操作,上篇文章中【MyBatis之启动分析(一)】有提到。

实例化 XMLConfigBuilder对象。

代码语言:javascript
复制
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;
      }
实例化 Configuration

通过 newConfiguration()的方式实例化:

typeAliasRegistry是一个类型别名注册器,实现原理就是维护一份 HashMap,别名作为 key,类的全限定名作为 value。这里将框架中使用的类注册到类型别名注册器中。TypeAliasRegistry.registerAlias代码如下:

代码语言:javascript
复制
public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    //  在验证是否存在key和保存kv前,统一将key转换成小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      // 当注册的类型已存在时,抛出异常
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // TYPE_ALIASES 为定义的一个HashMap
    TYPE_ALIASES.put(key, value);
    }

在实例化 Configuration类过程中,在该类里除了实例化了 TypeAliasRegistry还实例化了另外一个下面用到的的类:

代码语言:javascript
复制
// 类型处理器注册器
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

TypeHandlerRegistryTypeAliasRegistry实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。TypeHandlerRegistry的属性

代码语言:javascript
复制
// jdbc类型和TypeHandler的映射关系,key必须是JdbcType的枚举类型,读取结果集数据时,将jdbc类型转换成java类型
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
    // Java类型与JdbcType类型的键值对,存在一对多的映射关系
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
    // 没有相应的类型处理器时,使用的处理器
    private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
    // 类型处理器类类型和类型处理器的映射关系
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
    // 空处理器的值,用来做校验
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    // 默认枚举类型处理器
    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

TypeHandlerRegistry构造函数:

代码语言:javascript
复制
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(Reader.class, new ClobReaderTypeHandler());
        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(InputStream.class, new BlobInputStreamTypeHandler());
        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());

        // mybatis-typehandlers-jsr310
        // 是否包含日期,时间相关的Api,通过判断是否加载java.time.Clock作为依据
        if (Jdk.dateAndTimeApiExists) {
          this.register(Instant.class, InstantTypeHandler.class);
          this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
          this.register(LocalDate.class, LocalDateTypeHandler.class);
          this.register(LocalTime.class, LocalTimeTypeHandler.class);
          this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
          this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
          this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
          this.register(Month.class, MonthTypeHandler.class);
          this.register(Year.class, YearTypeHandler.class);
          this.register(YearMonth.class, YearMonthTypeHandler.class);
          this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
        }

        // issue #273
        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());
    }

里面调用了两个 register()重载方法, type+handler 参的 TypeHandlerRegistry.register(Class<T>javaType,TypeHandler<?extendsT>typeHandler)type+jdbc type+handler 参的 TypeHandlerRegistry.register(Class<T>type,JdbcTypejdbcType,TypeHandler<?extendsT>handler)

代码语言:javascript
复制
// java type + handler
    public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
        register((Type) javaType, typeHandler);
    }

    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
        if (mappedJdbcTypes != null) {
          for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            register(javaType, handledJdbcType, typeHandler);
          }
          if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
          }
        } else {
          register(javaType, null, typeHandler);
        }
    }

    // java type + jdbc type + handler
    public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
        register((Type) type, jdbcType, handler);
    }

    // type + handler 和 type + jdbc type + handler 最终都调用此方法
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
          // 当 javaType 不为空时, 获取 java 类型的的映射
          Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
          if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            // 若映射为空,新建一个映射关系
            map = new HashMap<JdbcType, TypeHandler<?>>();
            // 保存至类型处理器映射关系中
            TYPE_HANDLER_MAP.put(javaType, map);
          }
          // 保存jdbcType和处理器关系,完成 java类型,jdbc类型,处理器三者之间的注册
          map.put(jdbcType, handler);
        }
        // 保存处理器信息中
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }  
     
    // MappedJdbcTypes 注解
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface MappedJdbcTypes {
      JdbcType[] value();
      boolean includeNullJdbcType() default false;
    }
  • type+handler方法:先获取处理器的 MappedJdbcTypes注解(自定义处理器注解),若注解的 value值不为空时,由于该值为 JdbcType[]类型,所以 for循环 javaType+jdbcType+TypeHandler注册,若 includeNullJdbcTypejdbcType是否包含 null)为 true,默认值为 false,注册到相应映射中。若注解的 valuenull,直接调用注册操作,里面不会注册 type+jdbc type+handler关系。
  • type+jdbc type+handler方法:该方法将java类强制转换为 java.lang.reflect.Type类型,然后调用最终注册的方法。
调用父类 BaseBuilder的构造方法

BaseBuilder定义有三个属性

代码语言:javascript
复制
protected final Configuration configuration;
    // 类型别名注册器
    protected final TypeAliasRegistry typeAliasRegistry;
    // 类型处理器注册器
    protected final TypeHandlerRegistry typeHandlerRegistry;

BaseBuilder构造方法

代码语言:javascript
复制
public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

这里属性,就是上面讲解到的。

调用 XMLConfigBuilder.parse() 作为解析入口。

parse()实现配置文件是否解析过

代码语言:javascript
复制
public Configuration parse() {
        // 若parsed为true,配置文件解析过
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 标志已解析过
        parsed = true;
        // 从根节点 configuration 开始解析
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

解析 /configuration里的配置

代码语言:javascript
复制
private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

从上面源码中,不难看出这里是解析 /configuration中的各个子节点。

properties 节点解析
properties配置方式
代码语言:javascript
复制
<!-- 方法一 -->
    <properties>
        <property name="username" value="${jdbc.username}" />
    </properties>

    <!-- 方法二 -->
    <properties resource="xxxConfig.properties">
    </properties>

    <!-- 方法三 -->
    <properties url="file:///D:/xxxConfig.properties">
    </properties>
propertiesElement()方法
代码语言:javascript
复制
private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          // 获取 propertie 节点,并保存 Properties 中
          Properties defaults = context.getChildrenAsProperties();
          // 获取 resource 的值
          String resource = context.getStringAttribute("resource");
          // 获取 url 的值
          String url = context.getStringAttribute("url");
          if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
          }
          if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
          } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
          }
          Properties vars = configuration.getVariables();
          if (vars != null) {
            defaults.putAll(vars);
          }
          // 将解析的值保存到 XPathParser 中
          parser.setVariables(defaults);
          // 将解析的值保存到 Configuration 中
          configuration.setVariables(defaults);
        }
    }

从上面源码中, resourceurl的配置形式不允许同时存在,否则抛出 BuilderException异常。先解析 propertie的配置值,再解析 resourceurl的值。当 propertie存在与 resourceurl相同的 key时, propertie的配置会被覆盖,应为 Properties实现的原理就是继承的 Hashtable类来实现的。

settings 节点解析
settings配置方式
代码语言:javascript
复制
<settings>
        <setting name="cacheEnabled" value="true" />
        ......
    </settings>

设置中各项的意图、默认值(引用来源:w3cschool)

设置参数

描述

有效值

默认值

cacheEnabled

该配置影响的所有映射器中配置的缓存的全局开关。

true,false

true

lazyLoadingEnabled

延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。

true,false

false

aggressiveLazyLoading

当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。

true,false

multipleResultSetsEnabled

是否允许单一语句返回多结果集(需要兼容驱动)。

true,false

true

useColumnLabel

使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。

true,false

true

useGeneratedKeys

允许 JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。

true,false

False

autoMappingBehavior

指定 MyBatis 应如何自动映射列到字段或属性。NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。FULL 会自动映射任意复杂的结果集(无论是否嵌套)。

NONE, PARTIAL, FULL

PARTIAL

defaultExecutorType

配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新。

SIMPLE REUSE BATCH

SIMPLE

defaultStatementTimeout

设置超时时间,它决定驱动等待数据库响应的秒数。

Any positive integer

Not Set (null)

safeRowBoundsEnabled

允许在嵌套语句中使用分页(RowBounds)。

true,false

False

mapUnderscoreToCamelCase

是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。

true, false

False

localCacheScope

MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。

SESSION,STATEMENT

SESSION

jdbcTypeForNull

当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。

JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER

OTHER

lazyLoadTriggerMethods

指定哪个对象的方法触发一次延迟加载。

A method name list separated by commas

equals,clone,hashCode,toString

defaultScriptingLanguage

指定动态 SQL 生成的默认语言。

A type alias or fully qualified class name.

org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver

callSettersOnNulls

指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。

true,false

false

logPrefix

指定 MyBatis 增加到日志名称的前缀。Any String

Not set

logImpl

指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

SLF4J, LOG4J, LOG4J2, JDKLOGGING, COMMONSLOGGING, STDOUTLOGGING, NOLOGGING

Not set

proxyFactory

指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。

CGLIB JAVASSIST

CGLIB

settingsAsProperties()方法
代码语言:javascript
复制
private Properties settingsAsProperties(XNode context) {
        if (context == null) {
          return new Properties();
        }
        // 获取setting节点的name和value,并保存至Properties返回
        Properties props = context.getChildrenAsProperties();
        // Check that all settings are known to the configuration class
        // 创建Configuration的MetaClass
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        // 校验Configuration中是否有setting设置的name值
        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).");
          }
        }
        return props;
    }

这里获取到 setting的值,并返回 Properties对象。然后做配置的 name是否合法。org.apache.ibatis.reflection.MetaClass类是保存着一个利用反射获取到的类信息, metaConfig.hasSetter(String.valueOf(key))是判断 metaConfig对象中是否包含 key属性。

vfsImpl()方法
代码语言:javascript
复制
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
          String value = props.getProperty("vfsImpl");
        if (value != null) {
          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);
            }
          }
        }
    }

该方法是解析虚拟文件系统配置,用来加载自定义虚拟文件系统的资源。类保存在 Configuration.vfsImpl中。

settingsElement()方法

这个方法的作用就是将解析的 settings设置到 configuration

代码语言:javascript
复制
private void settingsElement(Properties props) throws Exception {
        configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
        configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
        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"), false));
        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")));
        @SuppressWarnings("unchecked")
        Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
        configuration.setDefaultEnumTypeHandler(typeHandler);
        configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
        configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
        configuration.setLogPrefix(props.getProperty("logPrefix"));
        @SuppressWarnings("unchecked")
        Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
        configuration.setLogImpl(logImpl);
        configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    }
typeAliases 节点解析
typeAliases配置方式
代码语言:javascript
复制
<typeAliases>
        <package name="com.ytao.main.model"/>
        // 或
        <typeAlias type="com.ytao.main.model.Student" alias="student"/>
        <typeAlias type="com.ytao.main.model.Person"/>
    </typeAliases>

该节点是配置类和别名的关系

  1. package节点是配置整个包下的类
  2. typeAlias节点是指定配置单个类, type为必填值且为类全限定名, alias为选填。配置后,是该类时,可直接使用别名。
typeAliasesElement()方法
代码语言:javascript
复制
private void typeAliasesElement(XNode parent) {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              // 以 package 方式配置
              String typeAliasPackage = child.getStringAttribute("name");
              configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
              // 以 alias 方式配置
              String alias = child.getStringAttribute("alias");
              String type = child.getStringAttribute("type");
              try {
                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);
              }
            }
          }
        }
    }
使用 package 配置

当扫描 package时,获取到包名后 TypeAliasRegistry.registerAliases(typeAliasPackage)

代码语言:javascript
复制
public void registerAliases(String packageName){
        registerAliases(packageName, Object.class);
    }

    public void registerAliases(String packageName, Class<?> superType){
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        // 获取 package 下所有已 .class 结尾的文件
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        // 获取扫描出来的类
        Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
        for(Class<?> type : typeSet){
          // Ignore inner classes and interfaces (including package-info.java)
          // Skip also inner classes. See issue #6
          // 过滤类
          if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            registerAlias(type);
          }
        }
    }

扫描到指定 package下所有以 .class结尾文件的类,并保存至Set集合中,然后遍历集合,过滤掉没有名称,接口,和底层特定类。最后 TypeAliasRegistry.registerAlias(Class<?>type)注册到别名注册器中。

代码语言:javascript
复制
public void registerAlias(Class<?> type) {
        // 使用类的 simpleName 作为别名,也就是默认的别名命名规则
        String alias = type.getSimpleName();
        Alias aliasAnnotation = type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
          alias = aliasAnnotation.value();
        } 
        // 上面分析的最终注册的方法
        registerAlias(alias, type);
    }

通过类注册到注册器中时,如果该注册类有使用 @Aliasorg.apache.ibatis.type.Alias)注解,那么XML配置中配置的别名会被注解配置覆盖。

使用 typeAlias 配置

如果 typeAliasalias有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的simpleName作为别名。

plugins 节点解析
plugins配置方式
代码语言:javascript
复制
<plugins>
        // 配置自定义插件,可指定在某个点进行拦截
        <plugin interceptor="com.ytao.main.plugin.DemoInterceptor">
            // 当前插件属性
            <property name="name" value="100"/>
        </plugin>
    </plugins>

自定义插件需要实现 org.apache.ibatis.plugin.Interceptor接口,同时在注解上指定拦截的方法。

pluginElement()方法
代码语言:javascript
复制
private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            // 获取自定插件的类名
            String interceptor = child.getStringAttribute("interceptor");
            // 获取插件属性
            Properties properties = child.getChildrenAsProperties();
            // 实例化 Interceptor
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // 设置插件属性到插件中
            interceptorInstance.setProperties(properties);
            // 将插件保存在 configuration 中
            configuration.addInterceptor(interceptorInstance);
          }
        }
    }

这里取 <plugin>节点的 interceptor可以使用别名设置。从源码中 resolveClass方法

代码语言:javascript
复制
//
    protected Class<?> resolveClass(String alias) {
        if (alias == null) {
          return null;
        }
        try {
          return resolveAlias(alias);
        } catch (Exception e) {
          throw new BuilderException("Error resolving class. Cause: " + e, e);
        }
    }

    //
    protected Class<?> resolveAlias(String alias) {
        return typeAliasRegistry.resolveAlias(alias);
    }

    //
    public <T> Class<T> resolveAlias(String string) {
        try {
          if (string == null) {
            return null;
          }
          // issue #748
          // 将传入的 类 名称统一转换
          String key = string.toLowerCase(Locale.ENGLISH);
          Class<T> value;
          // 验证别名中是否有当前传入的key
          if (TYPE_ALIASES.containsKey(key)) {
            value = (Class<T>) TYPE_ALIASES.get(key);
          } else {
            value = (Class<T>) Resources.classForName(string);
          }
          return value;
        } catch (ClassNotFoundException e) {
          throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
        }
    }

以上源码为别名解析过程,其他别名的解析也是调用此方法,先去保存的别名中去找,是否有别名,如果没有就通过 Resources.classForName生成实例。

objectFactory,objectWrapperFactory,reflectorFactory 节点解析

以上都是对实现类都是对MyBatis进行扩展。解析方法也类似,最后都是保存在 configuration

代码语言:javascript
复制
// objectFactory 解析
    private void objectFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties properties = context.getChildrenAsProperties();
          ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
          factory.setProperties(properties);
          configuration.setObjectFactory(factory);
        }
    }

    // objectWrapperFactory 解析
    private void objectWrapperFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
          configuration.setObjectWrapperFactory(factory);
        }
    }

    // reflectorFactory 解析
    private void reflectorFactoryElement(XNode context) throws Exception {
        if (context != null) {
           String type = context.getStringAttribute("type");
           ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
           configuration.setReflectorFactory(factory);
        }
    }

以上为解析 objectFactory,objectWrapperFactory,reflectorFactory源码,经过前面的分析后,这里比较容易看懂。

environments 节点解析
environments配置方式
代码语言:javascript
复制
<environments default="development">
        <environment id="development">
            <!-- 事务管理 -->
            <transactionManager type="JDBC">
                <property name="prop" value="100"/>
            </transactionManager>
            <!-- 数据源 -->
            <dataSource type="UNPOOLED">
                <!-- JDBC 驱动 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!-- 数据库的 url -->
                <property name="url" value="${jdbc.url}"/>
                <!-- 数据库登录名 -->
                <property name="username" value="${jdbc.username}"/>
                <!-- 数据库登录密码 -->
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <!-- 一个环境,对应一个environment -->
        ......
    </environments>

该节点可设置多个环境,针对不同的环境单独配置。environments的属性 default是默认环境,该值对应一个 environment的属性 id的值。

  • transactionManager为事务管理,属性 type为事务管理类型,上面的介绍的 newConfiguration()有定义类型有:JDBC 和 MANAGED事务管理类型。
  • dataSource是数据源, type为数据源类型,与 transactionManager同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED数据源类型。
environmentsElement()方法
代码语言:javascript
复制
private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
          if (environment == null) {
            environment = context.getStringAttribute("default");
          }
          for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            // 验证 id
            if (isSpecifiedEnvironment(id)) {
              // 解析 transactionManager, 并实例化 TransactionFactory
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
              // 解析 dataSource,并实例化 DataSourceFactory
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              // 获取 dataSource
              DataSource dataSource = dsFactory.getDataSource();
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
          }
        }
    }

    private boolean isSpecifiedEnvironment(String id) {
        if (environment == null) {
          throw new BuilderException("No environment specified.");
        } else if (id == null) {
          throw new BuilderException("Environment requires an id attribute.");
        } else if (environment.equals(id)) {
          return true;
        }
        return false;
    }

若没有配置 environment环境或环境没有给 id属性,则会抛出异常,若当前 id是要使用的就返回 true,否则返回 falseTransactionFactory实例化过程比较简单,与创建 DataSourceFactory类似。

数据源的获取

获取数据源,首先得创建 DataSourceFactory,上面使用 DataSourceFactorydsFactory=dataSourceElement(child.evalNode("dataSource"))创建

代码语言:javascript
复制
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties props = context.getChildrenAsProperties();
          DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
          factory.setProperties(props);
          return factory;
        }
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }

这里就是获取到数据源得 type后,利用上面所讲到得 resolveClass()方法获取到 DataSourceFactory。以 UNPOOLED为例,对应的 DataSourceFactory实现类为 UnpooledDataSourceFactory。实例化过程中就给该类的属性 dataSource数据源赋值了

代码语言:javascript
复制
/**
     * UnpooledDataSourceFactory 类
     */
    protected DataSource dataSource;

    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }

    @Override
    public DataSource getDataSource() {
       return dataSource;
    }

UnpooledDataSource类里面有静态代码块所以数据源被加载

代码语言:javascript
复制
/**
     * UnpooledDataSource 类
     */
    static {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
          Driver driver = drivers.nextElement();
          registeredDrivers.put(driver.getClass().getName(), driver);
        }
    }
databaseIdProvider 节点解析
databaseIdProvider配置方式
代码语言:javascript
复制
<databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
        <property name="MySQL" value="mysql"/>
    </databaseIdProvider>

    <select id="select" resultType="com.ytao.main.model.Student" databaseId="mysql">
        select
          *
        from student
    </select>

基于映射语句中的 databaseId属性,可以根据不同数据库厂商执行不同的sql。

databaseIdProviderElement()方法
代码语言:javascript
复制
private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // 保持向后兼容
      if ("VENDOR".equals(type)) {
          type = "DB_VENDOR";
      }
      Properties properties = context.getChildrenAsProperties();
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

根据匹配的数据库厂商类型匹配数据源 databaseIdProvider.getDatabaseId(environment.getDataSource())

代码语言:javascript
复制
@Override
  public String getDatabaseId(DataSource dataSource) {
    if (dataSource == null) {
      throw new NullPointerException("dataSource cannot be null");
    }
    try {
      return getDatabaseName(dataSource);
    } catch (Exception e) {
      log.error("Could not get a databaseId from dataSource", e);
    }
    return null;
  }

  private String getDatabaseName(DataSource dataSource) throws SQLException {
    // 根据数据源获取数据库产品名称
    String productName = getDatabaseProductName(dataSource);
    if (this.properties != null) {
      for (Map.Entry<Object, Object> property : properties.entrySet()) {
        // 判断是否包含,选择使用的数据库产品
        if (productName.contains((String) property.getKey())) {
          return (String) property.getValue();
        }
      }
      // no match, return null
      return null;
    }
    return productName;
  }

  private String getDatabaseProductName(DataSource dataSource) throws SQLException {
    Connection con = null;
    try {
      // 数据库连接
      con = dataSource.getConnection();
      // 获取连接元数据
      DatabaseMetaData metaData = con.getMetaData();
      // 获取数据库产品名称
      return metaData.getDatabaseProductName();
    } finally {
      if (con != null) {
        try {
          con.close();
        } catch (SQLException e) {
          // ignored
        }
      }
    }
  }

这里需要注意的是配置:比如使用 mysql,我踩过这里的坑,这里Name为 MySQL,我把 y写成大写,结果匹配不上。另外这里写个 My也能匹配上,应为是使用的 String.contains方法,只要包含就会符合,这里代码应该不够严谨。

typeHandlers 节点解析
typeHandlers配置方式
代码语言:javascript
复制
<typeHandlers>
        <package name="com.ytao.main.handler"/>
        // 或
        <typeHandler javaType="java.util.Date"  jdbcType="TIMESTAMP" handler="com.ytao.main.handler.DemoDateHandler" />
    </typeHandlers>

扫描整个包或者指定类型之间的映射, javaType, jdbcType非必需, handler必填项

typeHandlerElement()方法
代码语言:javascript
复制
private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // 获取包名
          String typeHandlerPackage = child.getStringAttribute("name");
          // 注册包下所有的类型处理器
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          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);
          }
        }
      }
    }
  }

源码分析会根据包下所有处理器或者指定处理器进行解析,最后会根据上面分析到的 type+handlertype+jdbc type+handler不同情况注册。另外这里还有个 TypeHandlerRegistry.register(Class<?>typeHandlerClass)注册类

代码语言:javascript
复制
public void register(Class<?> typeHandlerClass) {
    // 标志是否从 MappedTypes 注解中获取 javaType 注册
    boolean mappedTypeFound = false;
    // 获取 MappedTypes 的值
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        // 已 type + handler 的方式注册
        register(javaTypeClass, typeHandlerClass);
        // 标志已通过注解注册类型
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      // 通过 TypeHandler 注册
      register(getInstance(null, typeHandlerClass));
    }
  }

  // 实例化
  public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
      try {
        // 获取有参构造函数
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
        // 实例化对象
        return (TypeHandler<T>) c.newInstance(javaTypeClass);
      } catch (NoSuchMethodException ignored) {
        // ignored
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
      }
    }
    try {
      // 获取无参构造函数
      Constructor<?> c = typeHandlerClass.getConstructor();
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }  

  // 注册实例
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        register(handledType, typeHandler);
        mappedTypeFound = true;
      }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }

以上的 register方法中,了解 type+jdbc type+handler后,其他的 register重载方法比较容易理解,其他的都是基于它上面的封装。

mappers 节点解析
mappers配置方式
代码语言:javascript
复制
<mappers>
        <package name="com.ytao.main.mapper"/>
        // 或
        <mapper resource="mapper/studentMapper.xml"/>
        // 或
        <mapper url="file:///D:/mybatis-3-mybatis-3.4.6/src/main/resources/mapper/studentMapper.xml"/>
        // 或
        <mapper class="com.ytao.main.mapper.StudentMapper"/>
    </mappers>

可通过以上四种形式配置 mappers节点, <package><mapper>为互斥节点。

mapperElement()方法

该方法是负责解析 <mappers>节点

代码语言:javascript
复制
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 如果配置 package 节点,则扫描
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // 解析包下类Mapper接口,并注册到configuration的mapperRegistry中
          configuration.addMappers(mapperPackage);
        } else {
          // 获取mapper节点的resource,url,class属性
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 根据resource解析,并且url,class值必须为空,也就不能配置值。url,class同理,其它两个属性也不能配置值
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // 通过resource获取流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 创建XMLMapperBuilder对象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析映射配置文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // 通过url获取流
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 和resource解析方式一样,创建XMLMapperBuilder对象,然后解析映射配置文件
            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);
            // 将接口注册到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.");
          }
        }
      }
    }
  }

<package>的包扫描到的类,然后单个单个注册到configuration的mapperRegistry中,这里和 <mapper>使用 class属性是一样逻辑。解析 package方式

代码语言:javascript
复制
// Configuration 中定义了
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  /**
   * 步骤一
   * 该函数于 Configuration 中
   */  
  public void addMappers(String packageName) {
    // mapperRegistry定义在Configuration中的一个属性
    mapperRegistry.addMappers(packageName);
  }

  /**
   * 步骤二
   * 该函数于 MapperRegistry 中
   */   
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }

  /**
   * 步骤三
   * 该函数于 MapperRegistry 中
   */       
  public void addMappers(String packageName, Class<?> superType) {
    // 通过 ResolverUtil 获取包下的类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 遍历获取到的类,注册到 MapperRegistry
      addMapper(mapperClass);
    }
  }   

  /**
   * 步骤四
   * 该函数于 MapperRegistry 中
   */
  public <T> void addMapper(Class<T> type) {
    // mapper 类为 interface 接口
    if (type.isInterface()) {
      // 判断当前class是否已经注册过
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      // 校验是否加载完成
      boolean loadCompleted = false;
      try {
        // 保存 mapper 接口和 MapperProxyFactory 之间的映射
        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.
        // 解析xml和注解
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        // 标志加载完成
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

解析 mapperclass属性

代码语言:javascript
复制
// 该函数于 Configuration 中
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

  // ... 这里调用上面的【步骤四】

这两中方式是直接注册接口到 mapperRegistry,另外两种是解析 xml的方式就是获取映射文件的 namespace,再注册进来, XMLMapperBuilder是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。

这里对XMLConfigBuilder解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ytao 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 使用
    • 实例化 XMLConfigBuilder对象。
      • 实例化 Configuration
      • 调用父类 BaseBuilder的构造方法
    • 调用 XMLConfigBuilder.parse() 作为解析入口。
      • properties 节点解析
      • settings 节点解析
      • typeAliases 节点解析
      • plugins 节点解析
      • objectFactory,objectWrapperFactory,reflectorFactory 节点解析
      • environments 节点解析
      • databaseIdProvider 节点解析
      • typeHandlers 节点解析
      • mappers 节点解析
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档