前言
XMLConfigBuilder
是BaseBuilder
(解析中会涉及到讲解)的其中一个子类,它的作用是把 MyBatis 的 XML 及相关配置解析出来,然后保存到Configuration
中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。
<!– more –>
使用
调用 XMLConfigBuilder
进行解析,要进行两步操作,上篇文章中【MyBatis 之启动分析(一)】有提到。
实例化 XMLConfigBuilder
对象。
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
通过 new Configuration()
的方式实例化:typeAliasRegistry
是一个类型别名注册器,实现原理就是维护一份 HashMap
,别名作为key
,类的全限定名作为value
。这里将框架中使用的类注册到类型别名注册器中。TypeAliasRegistry.registerAlias
代码如下:
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
还实例化了另外一个下面用到的的类:
// 类型处理器注册器
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
TypeHandlerRegistry
和 TypeAliasRegistry
实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。TypeHandlerRegistry
的属性
// 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
构造函数:
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<? extends T> typeHandler)
和 type + jdbc type + handler
参的TypeHandlerRegistry.register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler)
// 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
注册,若includeNullJdbcType
(jdbcType
是否包含null
)为true
,默认值为false
, 注册到相应映射中。若注解的value
为null
,直接调用注册操作,里面不会注册type + jdbc type + handler
关系。 -
type + jdbc type + handler
方法:该方法将 java 类强制转换为java.lang.reflect.Type
类型,然后调用最终注册的方法。
调用父类 BaseBuilder
的构造方法
BaseBuilder
定义有三个属性
protected final Configuration configuration;
// 类型别名注册器
protected final TypeAliasRegistry typeAliasRegistry;
// 类型处理器注册器
protected final TypeHandlerRegistry typeHandlerRegistry;
BaseBuilder
构造方法
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();}
这里属性,就是上面讲解到的。
调用 XMLConfigBuilder.parse()
作为解析入口。
parse()
实现配置文件是否解析过
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
里的配置
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
配置方式
<!-- 方法一 -->
<properties>
<property name="username" value="${jdbc.username}" />
</properties>
<!-- 方法二 -->
<properties resource="xxxConfig.properties">
</properties>
<!-- 方法三 -->
<properties url="file:///D:/xxxConfig.properties">
</properties>
propertiesElement()
方法
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);
}
}
从上面源码中,resource
和 url
的配置形式不允许同时存在,否则抛出 BuilderException
异常。先解析 propertie
的配置值,再解析 resource
或url
的值。
当 propertie
存在与 resource
或url
相同的 key
时,propertie
的配置会被覆盖,应为 Properties
实现的原理就是继承的 Hashtable
类来实现的。
settings 节点解析
settings
配置方式
<settings>
<setting name="cacheEnabled" value="true" />
......
</settings>
设置中各项的意图、默认值 图(引用来源:w3cschool)
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true,false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true,false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true,false,true | |
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, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING | Not set |
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB JAVASSIST | CGLIB |
settingsAsProperties()
方法
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()
方法
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
中
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
配置方式
<typeAliases>
<package name="com.ytao.main.model"/>
// 或
<typeAlias type="com.ytao.main.model.Student" alias="student"/>
<typeAlias type="com.ytao.main.model.Person"/>
</typeAliases>
该节点是配置类和别名的关系
-
package
节点是配置整个包下的类 -
typeAlias
节点是指定配置单个类,type
为必填值且为类全限定名,alias
为选填。
配置后,是该类时,可直接使用别名。
typeAliasesElement()
方法
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)
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)
注册到别名注册器中。
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);
}
通过类注册到注册器中时,如果该注册类有使用@Alias
(org.apache.ibatis.type.Alias
)注解,那么 XML 配置中配置的别名会被注解配置覆盖。
使用 typeAlias 配置
如果 typeAlias
的alias
有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的 simpleName 作为别名。
plugins 节点解析
plugins
配置方式
<plugins>
// 配置自定义插件,可指定在某个点进行拦截
<plugin interceptor="com.ytao.main.plugin.DemoInterceptor">
// 当前插件属性
<property name="name" value="100"/>
</plugin>
</plugins>
自定义插件需要实现 org.apache.ibatis.plugin.Interceptor
接口, 同时在注解上指定拦截的方法。
pluginElement()
方法
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
方法
//
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
。
// 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
配置方式
<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
为事务管理类型,上面的介绍的new Configuration()
有定义类型有:JDBC 和 MANAGED 事务管理类型。 -
dataSource
是数据源,type
为数据源类型,与transactionManager
同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED 数据源类型。
environmentsElement()
方法
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
,否则返回false
。TransactionFactory
实例化过程比较简单,与创建 DataSourceFactory
类似。
数据源的获取
获取数据源,首先得创建 DataSourceFactory
, 上面使用DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))
创建
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
数据源赋值了
/**
* UnpooledDataSourceFactory 类
*/
protected DataSource dataSource;
public UnpooledDataSourceFactory() {this.dataSource = new UnpooledDataSource();
}
@Override
public DataSource getDataSource() {return dataSource;}
UnpooledDataSource
类里面有静态代码块所以数据源被加载
/**
* UnpooledDataSource 类
*/
static {Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
databaseIdProvider 节点解析
databaseIdProvider
配置方式
<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()
方法
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())
@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
配置方式
<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()
方法
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 + handler
和type + jdbc type + handler
不同情况注册。
另外这里还有个 TypeHandlerRegistry.register(Class<?> typeHandlerClass)
注册类
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
配置方式
<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>
节点
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
方式
// 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);
}
}
}
}
解析 mapper
的class
属性
// 该函数于 Configuration 中
public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);
}
// ... 这里调用上面的【步骤四】
这两中方式是直接注册接口到 mapperRegistry
, 另外两种是解析xml
的方式就是获取映射文件的 namespace
,再注册进来,XMLMapperBuilder
是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。
这里对 XMLConfigBuilder 解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。
个人博客:https://ytao.top
我的公众号 ytao