XMLConfigBuilder
是BaseBuilder
(解析中會涉及到講解)的其中一個子類,它的做用是把MyBatis的XML及相關配置解析出來,而後保存到Configuration
中。本文就解析過程按照執行順序進行分析,掌握經常使用配置的解析原理。java
調用XMLConfigBuilder
進行解析,要進行兩步操做,上篇文章中【MyBatis之啓動分析(一)】有提到。mysql
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()
的方式實例化: sql
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
的屬性apache
// 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)
mybatis
// 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
定義有三個屬性oracle
protected final Configuration configuration;
// 類型別名註冊器
protected final TypeAliasRegistry typeAliasRegistry;
// 類型處理器註冊器
protected final TypeHandlerRegistry typeHandlerRegistry;
複製代碼
BaseBuilder
構造方法app
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>
<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>
<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>
<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
時,獲取到包名後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
的alias
有設置值,使用自定名稱方式註冊,不然使用默認方式註冊,即類的simpleName做爲別名。
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
生成實例。
以上都是對實現類都是對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 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 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>
<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>
<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解析配置文件到此分析完,本文對配置文件解析的流程大體瞭解流程和原理。相信遇到配置問題異常,大體能排查到根本緣由。
個人公衆號 ytao