Mybatis技術內幕:初始化之加載 mybatis-config

概述

在 MyBatis 初始化過程當中,會加載mybatis-config.xml配置文件、映射配置文件以及 Mapper 接口中的註解信息,解析後的配置信息會造成相應的對象並保存到Configuration 對象中。例如:html

  • resultMap節點(即 ResultSet 的映射規則) 會被解析成 ResultMap 對象
  • result 節點(即屬性映射)會被解析成 ResultMapping 對象。以後,利用該 Configuration 對象建立SqlSessionFactory對象。待MyBatis初始化以後,開發人員能夠經過初始化獲得 SqlSessionFactory 建立 SqlSession對象並完成數據庫操做。
  • 對應 builder 模塊,爲配置解析過程
  • 對應 mapping 模塊,主要爲 SQL 操做解析後的映射。

由於整個 MyBatis 的初始化流程涉及代碼頗多,因此拆分紅三篇文章:java

  • 加載 mybatis-config.xml 配置文件。
  • 加載 Mapper 映射配置文件。
  • 加載 Mapper 接口中的註解信息。

本文就主要分享第一部分「加載 mybatis-config.xml 配置文件」。正則表達式

1.入口

MyBatis 的初始化流程的入口是 SqlSessionFactoryBuilder#build(Reader reader, String environment, Properties properties) 方法,代碼以下:數據庫

// SqlSessionFactoryBuilder 中,build 方法有多種重載方式。這裏就選取一個。
// SqlSessionFactoryBuilder.java

/** * 構造 SqlSessionFactory 對象 * * @param reader Reader 對象 * @param environment 環境 * @param properties Properties 變量 * @return SqlSessionFactory 對象 */
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        // <1> 建立 XMLConfigBuilder 對象
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        // <2> 執行 XML 解析
        // <3> 建立 DefaultSqlSessionFactory 對象
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            reader.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}
複製代碼
  • <1> 處,建立 XMLConfigBuilder 對象。
  • <2> 處,調用 XMLConfigBuilder#parse()方法,執行XML解析,返回Configuration 對象。
  • <3> 處,建立 DefaultSqlSessionFactory 對象。

本文的重點是 <1><2> 處,即 XMLConfigBuilder 類。express

2.BaseBuilder

org.apache.ibatis.builder.BaseBuilder基礎構造器抽象類,爲子類提供通用的工具類。
爲何不直接講 XMLConfigBuilder ,而是先講BaseBuilder呢?由於,BaseBuilder 是 XMLConfigBuilder 的父類,而且它還有其餘的子類。以下圖所示: apache

2.1 構造方法

// BaseBuilder.java

/** * MyBatis Configuration 對象 */
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;

public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
複製代碼

configuration 屬性,MyBatis-Configuration對象。XML和註解中解析到的配置,最終都會設置到 org.apache.ibatis.session.Configuration數組

2.2 parseExpression

#parseExpression(String regex,String defaultValue)方法,建立正則表達式。代碼以下:bash

// BaseBuilder.java

/** * 建立正則表達式 * * @param regex 指定表達式 * @param defaultValue 默認表達式 * @return 正則表達式 */
@SuppressWarnings("SameParameterValue")
protected Pattern parseExpression(String regex, String defaultValue) {
    return Pattern.compile(regex == null ? defaultValue : regex);
}
複製代碼

2.3 xxxValueOf

#xxxValueOf(...) 方法,將字符串轉換成對應的數據類型的值。代碼以下:session

// BaseBuilder.java

protected Boolean booleanValueOf(String value, Boolean defaultValue) {
    return value == null ? defaultValue : Boolean.valueOf(value);
}

protected Integer integerValueOf(String value, Integer defaultValue) {
    return value == null ? defaultValue : Integer.valueOf(value);
}

protected Set<String> stringSetValueOf(String value, String defaultValue) {
    value = (value == null ? defaultValue : value);
    return new HashSet<>(Arrays.asList(value.split(",")));
}
複製代碼

2.4 resolveJdbcType

#resolveJdbcType(String alias) 方法,解析對應的 JdbcType類型。代碼以下:mybatis

// BaseBuilder.java

protected JdbcType resolveJdbcType(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return JdbcType.valueOf(alias);
    } catch (IllegalArgumentException e) {
        throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);
    }
}
複製代碼

2.5 resolveResultSetType

#resolveResultSetType(String alias) 方法,解析對應的 ResultSetType 類型。代碼以下:

// BaseBuilder.java

protected ResultSetType resolveResultSetType(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return ResultSetType.valueOf(alias);
    } catch (IllegalArgumentException e) {
        throw new BuilderException("Error resolving ResultSetType. Cause: " + e, e);
    }
}
複製代碼

2.6 resolveParameterMode

#resolveParameterMode(String alias) 方法,解析對應的 ParameterMode 類型。代碼以下:

// BaseBuilder.java

protected ParameterMode resolveParameterMode(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return ParameterMode.valueOf(alias);
    } catch (IllegalArgumentException e) {
        throw new BuilderException("Error resolving ParameterMode. Cause: " + e, e);
    }
}
複製代碼

2.7 createInstance

#createInstance(String alias) 方法,建立指定對象。代碼以下:

// BaseBuilder.java

protected Object createInstance(String alias) {
    // <1> 得到對應的類型
    Class<?> clazz = resolveClass(alias);
    if (clazz == null) {
        return null;
    }
    try {
        // <2> 建立對象
        return resolveClass(alias).newInstance(); // 這裏重複得到了一次
    } catch (Exception e) {
        throw new BuilderException("Error creating instance. Cause: " + e, e);
    }
}
複製代碼

<1> 處,調用#resolveClass(String alias)方法,得到對應的類型。代碼以下:

// BaseBuilder.java

protected <T> Class<? extends T> 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 <T> Class<? extends T> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
}
複製代碼

從 typeAliasRegistry 中,經過別名或類全名,得到對應的類
<2> 處,建立對象。

2.8 resolveTypeHandler

#resolveTypeHandler(Class<?> javaType, String typeHandlerAlias)方法,從 typeHandlerRegistry 中得到或建立對應的 TypeHandler 對象。代碼以下:

// BaseBuilder.java

protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerType) {
    if (typeHandlerType == null) {
        return null;
    }
    // javaType ignored for injected handlers see issue #746 for full detail
    // 先得到 TypeHandler 對象
    TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
    if (handler == null) { // 若是不存在,進行建立 TypeHandler 對象
        // not in registry, create a new one
        handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType);
    }
    return handler;
}
複製代碼

3. XMLConfigBuilder

org.apache.ibatis.builder.xml.XMLConfigBuilder繼承BaseBuilder抽象類,XML 配置構建器,主要負責解析mybatis-config.xml配置文件。即對應《MyBatis文檔——XML映射配置文件》

3.1 構造方法

// XMLConfigBuilder.java

/** * 是否已解析 */
private boolean parsed;
/** * 基於 Java XPath 解析器 */
private final XPathParser parser;
/** * 環境 */
private String environment;
/** * ReflectorFactory 對象 */
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

public XMLConfigBuilder(Reader reader) {
    this(reader, null, null);
}

public XMLConfigBuilder(Reader reader, String environment) {
    this(reader, environment, null);
}

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}

public XMLConfigBuilder(InputStream inputStream) {
    this(inputStream, null, null);
}

public XMLConfigBuilder(InputStream inputStream, String environment) {
    this(inputStream, environment, null);
}

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // <1> 建立 Configuration 對象
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // <2> 設置 Configuration 的 variables 屬性
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}
複製代碼
  • parser 屬性,XPathParser 對象。在 《精盡 MyBatis 源碼分析 —— 解析器模塊》 中,已經詳細解析。
  • localReflectorFactory 屬性,DefaultReflectorFactory 對象。在 《精盡 MyBatis 源碼分析 —— 反射模塊》 中,已經詳細解析。
    構造方法重載了比較多,只須要看最後一個。

<1> 處,建立 Configuration 對象
<2> 處,設置 Configuration 對象的 variables 屬性。代碼以下:

// Configuration.java

/** * 變量 Properties 對象。 * * 參見 {@link org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement(XNode context)} 方法 */
protected Properties variables = new Properties();

public void setVariables(Properties variables) {
    this.variables = variables;
}
複製代碼

3.2 parse

#parse() 方法,解析 XML 成 Configuration 對象。代碼以下:

// XMLConfigBuilder.java

public Configuration parse() {
    // <1.1> 若已解析,拋出 BuilderException 異常
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // <1.2> 標記已解析
    parsed = true;
    // <2> 解析 XML configuration 節點
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
複製代碼

<1.1> 處,若已解析,拋出 BuilderException 異常
<1.2> 處,標記已解析
<2> 處,調用 XPathParser#evalNode(String expression)方法,得到XML<configuration /> 節點,後調用 #parseConfiguration(XNode root) 方法,解析該節點。詳細解析,見 「3.3 parseConfiguration」

3.3 parseConfiguration

#parseConfiguration(XNode root) 方法,解析 <configuration /> 節點。代碼以下:

// XMLConfigBuilder.java

private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        // <1> 解析 <properties /> 標籤
        propertiesElement(root.evalNode("properties"));
        // <2> 解析 <settings /> 標籤
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        // <3> 加載自定義 VFS 實現類
        loadCustomVfs(settings);
        // <4> 解析 <typeAliases /> 標籤
        typeAliasesElement(root.evalNode("typeAliases"));
        // <5> 解析 <plugins /> 標籤
        pluginElement(root.evalNode("plugins"));
        // <6> 解析 <objectFactory /> 標籤
        objectFactoryElement(root.evalNode("objectFactory"));
        // <7> 解析 <objectWrapperFactory /> 標籤
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // <8> 解析 <reflectorFactory /> 標籤
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // <9> 賦值 <settings /> 到 Configuration 屬性
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        // <10> 解析 <environments /> 標籤
        environmentsElement(root.evalNode("environments"));
        // <11> 解析 <databaseIdProvider /> 標籤
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // <12> 解析 <typeHandlers /> 標籤
        typeHandlerElement(root.evalNode("typeHandlers"));
        // <13> 解析 <mappers /> 標籤
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}
複製代碼

<1> 處,調用 #propertiesElement(XNode context) 方法,解析 <properties /> 節點。詳細解析,見 「3.3.1 propertiesElement」.
<2> 處,調用 #settingsAsProperties(XNode context) 方法,解析 <settings /> 節點。詳細解析,見 「3.3.2 settingsAsProperties」.
<3> 處,調用 #loadCustomVfs(Properties settings) 方法,加載自定義 VFS 實現類。詳細解析,見 「3.3.3 loadCustomVfs」.
<4> 處,調用 #typeAliasesElement(XNode parent) 方法,解析 <typeAliases /> 節點。詳細解析,見 「3.3.4 typeAliasesElement」.
<5> 處,調用 #typeAliasesElement(XNode parent)方法,解析 <plugins /> 節點。詳細解析,見 「3.3.5 pluginElement」.
<6> 處,調用 #objectFactoryElement(XNode parent) 方法,解析 <objectFactory /> 節點。詳細解析,見 「3.3.6 pluginElement」.
<7> 處,調用 #objectWrapperFactoryElement(XNode parent) 方法,解析 <objectWrapperFactory /> 節點。詳細解析,見 「3.3.7 objectWrapperFactoryElement」.
<8> 處,調用 #reflectorFactoryElement(XNode parent) 方法,解析 <reflectorFactory /> 節點。詳細解析,見 「3.3.8 reflectorFactoryElement」
<9> 處,調用 #settingsElement(Properties props) 方法,賦值 <settings /> 到 Configuration 屬性。詳細解析,見 「3.3.9 settingsElement」
<10> 處,調用 #environmentsElement(XNode context) 方法,解析 <environments /> 標籤。詳細解析,見 「3.3.10 environmentsElement」
<11> 處,調用 #databaseIdProviderElement(XNode context) 方法,解析 <databaseIdProvider /> 標籤。詳細解析,見 「3.3.11 databaseIdProviderElement」
<12> 處,調用 #typeHandlerElement(XNode context) 方法,解析 <typeHandlers /> 標籤。詳細解析,見 「3.3.12 typeHandlerElement」
<13> 處,調用 #mapperElement(XNode context) 方法,解析 <mappers /> 標籤。詳細解析,見 「3.3.13 mapperElement」

3.3.1 propertiesElement

#propertiesElement(XNode context) 方法,解析 <properties /> 節點。大致邏輯以下:

解析 <properties />標籤,成Properties對象。覆蓋configuration中的 Properties 對象到上面的結果。設置結果到 parserconfiguration 中。代碼以下:

// XMLConfigBuilder.java

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // 讀取子標籤們,爲 Properties 對象
        Properties defaults = context.getChildrenAsProperties();
        // 讀取 resource 和 url 屬性
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) { // resource 和 url 都存在的狀況下,拋出 BuilderException 異常
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
        }
        // 讀取本地 Properties 配置文件到 defaults 中。
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
            // 讀取遠程 Properties 配置文件到 defaults 中。
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        // 覆蓋 configuration 中的 Properties 對象到 defaults 中。
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        // 設置 defaults 到 parser 和 configuration 中。
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}
複製代碼

3.3.2 settingsAsProperties

#settingsElement(Properties props) 方法,將 <setting />標籤解析爲 Properties 對象。代碼以下:

// XMLConfigBuilder.java

private Properties settingsAsProperties(XNode context) {
    // 將子標籤,解析成 Properties 對象
    if (context == null) {
        return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    // 校驗每一個屬性,在 Configuration 中,有相應的 setting 方法,不然拋出 BuilderException 異常
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    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;
}
複製代碼

3.3.3 loadCustomVfs

#loadCustomVfs(Properties settings)方法,加載自定義VFS實現類。代碼以下:

// XMLConfigBuilder.java

private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    // 得到 vfsImpl 屬性
    String value = props.getProperty("vfsImpl");
    if (value != null) {
        // 使用 , 做爲分隔符,拆成 VFS 類名的數組
        String[] clazzes = value.split(",");
        // 遍歷 VFS 類名的數組
        for (String clazz : clazzes) {
            if (!clazz.isEmpty()) {
                // 得到 VFS 類
                @SuppressWarnings("unchecked")
                Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
                // 設置到 Configuration 中
                configuration.setVfsImpl(vfsImpl);
            }
        }
    }
}

// Configuration.java

/** * VFS 實現類 */
protected Class<? extends VFS> vfsImpl;

public void setVfsImpl(Class<? extends VFS> vfsImpl) {
    if (vfsImpl != null) {
        // 設置 vfsImpl 屬性
        this.vfsImpl = vfsImpl;
        // 添加到 VFS 中的自定義 VFS 類的集合
        VFS.addImplClass(this.vfsImpl);
    }
}
複製代碼

3.3.4 typeAliasesElement

#typeAliasesElement(XNode parent) 方法,解析 <typeAliases /> 標籤,將配置類註冊到 typeAliasRegistry 中。代碼以下:

// XMLConfigBuilder.java

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        // 遍歷子節點
        for (XNode child : parent.getChildren()) {
            // 指定爲包的狀況下,註冊包下的每一個類
            if ("package".equals(child.getName())) {
                String typeAliasPackage = child.getStringAttribute("name");
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            // 指定爲類的狀況下,直接註冊類和別名
            } else {
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class<?> clazz = Resources.classForName(type); // 得到類是否存在
                    // 註冊到 typeAliasRegistry 中
                    if (alias == null) {
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) { // 若類不存在,則拋出 BuilderException 異常
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}
複製代碼

3.3.5 pluginElement

#pluginElement(XNode parent) 方法,解析 <plugins /> 標籤,添加到 Configuration#interceptorChain 中。代碼以下:

// XMLConfigBuilder.java

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        // 遍歷 <plugins /> 標籤
        for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            // <1> 建立 Interceptor 對象,並設置屬性
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            // <2> 添加到 configuration 中
            configuration.addInterceptor(interceptorInstance);
        }
    }
}
複製代碼

<1> 處,建立 Interceptor 對象,並設置屬性。關於 Interceptor 類,後續文章,詳細解析。
<2> 處,調用 Configuration#addInterceptor(Interceptor interceptor) 方法,添加到 configuration 中。代碼以下:

// Configuration.java

/** * 攔截器鏈 */
protected final InterceptorChain interceptorChain = new InterceptorChain();

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}
複製代碼
  • 關於 InterceptorChain 類,後續文章,詳細解析。

3.3.6 objectFactoryElement

#objectFactoryElement(XNode parent) 方法,解析 <objectFactory /> 節點。代碼以下:

// XMLConfigBuilder.java

private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
        // 得到 ObjectFactory 的實現類
        String type = context.getStringAttribute("type");
        // 得到 Properties 屬性
        Properties properties = context.getChildrenAsProperties();
        // <1> 建立 ObjectFactory 對象,並設置 Properties 屬性
        ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
        factory.setProperties(properties);
        // <2> 設置 Configuration 的 objectFactory 屬性
        configuration.setObjectFactory(factory);
    }
}
複製代碼

<1> 處,建立 ObjectFactory 對象,並設置 Properties 屬性。
<2> 處,調用 Configuration#setObjectFactory(ObjectFactory objectFactory) 方法,設置 ConfigurationobjectFactory 屬性。代碼以下:

// Configuration.java

/** * ObjectFactory 對象 */
protected ObjectFactory objectFactory = new DefaultObjectFactory();

public void setObjectFactory(ObjectFactory objectFactory) {
    this.objectFactory = objectFactory;
}
複製代碼

3.3.7 objectWrapperFactoryElement

#objectWrapperFactoryElement(XNode context) 方法,解析 <objectWrapperFactory /> 節點。代碼以下:

// XMLConfigBuilder.java

private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        // 得到 ObjectFactory 的實現類
        String type = context.getStringAttribute("type");
        // <1> 建立 ObjectWrapperFactory 對象
        ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
        // 設置 Configuration 的 objectWrapperFactory 屬性
        configuration.setObjectWrapperFactory(factory);
    }
}
複製代碼

<1> 處,建立 ObjectWrapperFactory 對象。
<2> 處,調用 Configuration#setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) 方法,設置 Configuration 的 objectWrapperFactory 屬性。代碼以下:

// Configuration.java

/** * ObjectWrapperFactory 對象 */
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
    this.objectWrapperFactory = objectWrapperFactory;
}
複製代碼

3.3.8 reflectorFactoryElement

#reflectorFactoryElement(XNode parent) 方法,解析 <reflectorFactory /> 節點。代碼以下:

// XMLConfigBuilder.java

private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
        // 得到 ReflectorFactory 的實現類
        String type = context.getStringAttribute("type");
        // 建立 ReflectorFactory 對象
        ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
        // 設置 Configuration 的 reflectorFactory 屬性
        configuration.setReflectorFactory(factory);
    }
}
複製代碼

<1> 處,建立 ReflectorFactory 對象。 <2> 處,調用 Configuration#setReflectorFactory(ReflectorFactory reflectorFactory) 方法,設置 Configuration 的 reflectorFactory 屬性。代碼以下:

// Configuration.java

/** * ReflectorFactory 對象 */
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();

public void setReflectorFactory(ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
}
複製代碼

3.3.9 settingsElement

#settingsElement(Properties props) 方法,賦值 <settings /> 到 Configuration 屬性。代碼以下:

// XMLConfigBuilder.java

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 = 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 = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
複製代碼

屬性比較多,瞟一眼就行。

3.3.10 environmentsElement

#environmentsElement(XNode context) 方法,解析 <environments /> 標籤。代碼以下:

// XMLConfigBuilder.java

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        // <1> environment 屬性非空,從 default 屬性得到
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        // 遍歷 XNode 節點
        for (XNode child : context.getChildren()) {
            // <2> 判斷 environment 是否匹配
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                // <3> 解析 `<transactionManager />` 標籤,返回 TransactionFactory 對象
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // <4> 解析 `<dataSource />` 標籤,返回 DataSourceFactory 對象
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                DataSource dataSource = dsFactory.getDataSource();
                // <5> 建立 Environment.Builder 對象
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource);
                // <6> 構造 Environment 對象,並設置到 configuration 中
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
複製代碼

<1> 處,若 environment 屬性非空,從 default 屬性種得到 environment 屬性。
<2> 處,遍歷 XNode 節點,得到其 id 屬性,後調用 #isSpecifiedEnvironment(String id) 方法,判斷 environment 和 id 是否匹配。代碼以下:

// XMLConfigBuilder.java

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;
}
複製代碼

<3> 處,調用 #transactionManagerElement(XNode context) 方法,解析 <transactionManager /> 標籤,返回 TransactionFactory 對象。代碼以下:

// XMLConfigBuilder.java

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
        // 得到 TransactionFactory 的類
        String type = context.getStringAttribute("type");
        // 得到 Properties 屬性
        Properties props = context.getChildrenAsProperties();
        // 建立 TransactionFactory 對象,並設置屬性
        TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
        factory.setProperties(props);
        return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
複製代碼

<4> 處,調用 #dataSourceElement(XNode context) 方法,解析 <dataSource /> 標籤,返回 DataSourceFactory 對象,然後返回 DataSource 對象。代碼以下:

// XMLConfigBuilder.java

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
        // 得到 DataSourceFactory 的類
        String type = context.getStringAttribute("type");
        // 得到 Properties 屬性
        Properties props = context.getChildrenAsProperties();
        // 建立 DataSourceFactory 對象,並設置屬性
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
        factory.setProperties(props);
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
複製代碼

<5> 處,建立 Environment.Builder 對象。
<6> 處,構造 Environment 對象,並設置到 configuration 中。代碼以下:

// Configuration.java

/** * DB Environment 對象 */
protected Environment environment;

public void setEnvironment(Environment environment) {
    this.environment = environment;
}
複製代碼

3.3.10.1 Environment

org.apache.ibatis.mapping.EnvironmentDB 環境。代碼以下:

// Environment.java

public final class Environment {

    /** * 環境變好 */
    private final String id;
    /** * TransactionFactory 對象 */
    private final TransactionFactory transactionFactory;
    /** * DataSource 對象 */
    private final DataSource dataSource;

    public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
        if (id == null) {
            throw new IllegalArgumentException("Parameter 'id' must not be null");
        }
        if (transactionFactory == null) {
            throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
        }
        this.id = id;
        if (dataSource == null) {
            throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
        }
        this.transactionFactory = transactionFactory;
        this.dataSource = dataSource;
    }

    /** * 構造器 */
    public static class Builder {

        /** * 環境變好 */
        private String id;
        /** * TransactionFactory 對象 */
        private TransactionFactory transactionFactory;
        /** * DataSource 對象 */
        private DataSource dataSource;

        public Builder(String id) {
            this.id = id;
        }

        public Builder transactionFactory(TransactionFactory transactionFactory) {
            this.transactionFactory = transactionFactory;
            return this;
        }

        public Builder dataSource(DataSource dataSource) {
            this.dataSource = dataSource;
            return this;
        }

        public String id() {
            return this.id;
        }

        public Environment build() {
            return new Environment(this.id, this.transactionFactory, this.dataSource);
        }

    }

    public String getId() {
        return this.id;
    }

    public TransactionFactory getTransactionFactory() {
        return this.transactionFactory;
    }

    public DataSource getDataSource() {
        return this.dataSource;
    }

}
複製代碼

3.3.11 databaseIdProviderElement

#databaseIdProviderElement(XNode context) 方法,解析 <databaseIdProvider /> 標籤。代碼以下:

// XMLConfigBuilder.java

private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        // <1> 得到 DatabaseIdProvider 的類
        String type = context.getStringAttribute("type");
        // awful patch to keep backward compatibility 保持兼容
        if ("VENDOR".equals(type)) {
            type = "DB_VENDOR";
        }
        // <2> 得到 Properties 對象
        Properties properties = context.getChildrenAsProperties();
        // <3> 建立 DatabaseIdProvider 對象,並設置對應的屬性
        databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
        databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        // <4> 得到對應的 databaseId 編號
        String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
        // <5> 設置到 configuration 中
        configuration.setDatabaseId(databaseId);
    }
}
複製代碼

不瞭解的胖友,能夠先看看 《MyBatis 文檔 —— XML 映射配置文件 —— databaseIdProvider 數據庫廠商標識》 。 <1> 處,得到 DatabaseIdProvider 的類。 <2> 處,得到 Properties 對象。 <3> 處,建立 DatabaseIdProvider 對象,並設置對應的屬性。 <4> 處,調用 DatabaseIdProvider#getDatabaseId(DataSource dataSource) 方法,得到對應的 databaseId 標識。 <5> 處,設置到 configuration 中。代碼以下:

// Configuration.java

/** * 數據庫標識 */
protected String databaseId;

public void setDatabaseId(String databaseId) {
    this.databaseId = databaseId;
}
複製代碼

3.3.11.1 DatabaseIdProvider

org.apache.ibatis.mapping.DatabaseIdProvider數據庫標識提供器接口。代碼以下:

public interface DatabaseIdProvider {

    /** * 設置屬性 * * @param p Properties 對象 */
    void setProperties(Properties p);

    /** * 得到數據庫標識 * * @param dataSource 數據源 * @return 數據庫標識 * @throws SQLException 當 DB 發生異常時 */
    String getDatabaseId(DataSource dataSource) throws SQLException;

}
複製代碼

3.3.11.2 VendorDatabaseIdProvider

org.apache.ibatis.mapping.VendorDatabaseIdProvider實現 DatabaseIdProvider 接口,供應商數據庫標識提供器實現類。

① 構造方法

// VendorDatabaseIdProvider.java

/**
 * Properties 對象
 */
private Properties properties;

@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;
}

@Override
public void setProperties(Properties p) {
    this.properties = p;
}
複製代碼

② 得到數據庫標識

#getDatabaseId(DataSource dataSource) 方法,代碼以下:

// VendorDatabaseIdProvider.java

@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 { // <1> 得到數據庫產品名 String productName = getDatabaseProductName(dataSource); if (this.properties != null) { for (Map.Entry<Object, Object> property : properties.entrySet()) { // 若是產品名包含 KEY ,則返回對應的 VALUE if (productName.contains((String) property.getKey())) { return (String) property.getValue(); } } // no match, return null return null; } // <3> 不存在 properties ,則直接返回 productName return productName; } <1> 處,調用 #getDatabaseProductName(DataSource dataSource) 方法,得到數據庫產品名。代碼以下:

// VendorDatabaseIdProvider.java

private String getDatabaseProductName(DataSource dataSource) throws SQLException { try (Connection con = dataSource.getConnection()) { // 得到數據庫鏈接 DatabaseMetaData metaData = con.getMetaData(); // 得到數據庫產品名 return metaData.getDatabaseProductName(); } } 經過從 Connection 得到數據庫產品名。 <2> 處,若是 properties 非空,則從 properties 中匹配 KEY ?若成功,則返回 VALUE ,不然,返回 null 。 <3> 處,若是 properties 爲空,則直接返回 productName 。

3.3.12 typeHandlerElement

#typeHandlerElement(XNode parent) 方法,解析 <typeHandlers /> 標籤。代碼以下:

// XMLConfigBuilder.java

private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
        // 遍歷子節點
        for (XNode child : parent.getChildren()) {
            // <1> 若是是 package 標籤,則掃描該包
            if ("package".equals(child.getName())) {
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            // <2> 若是是 typeHandler 標籤,則註冊該 typeHandler 信息
            } else {
                // 得到 javaType、jdbcType、handler
                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); // 非空
                // 註冊 typeHandler
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}
複製代碼

遍歷子節點,分別處理 <1> 是 和 <2> 是 兩種標籤的狀況。邏輯比較簡單,最終都是註冊到 typeHandlerRegistry 中。

3.3.13 mapperElement

#mapperElement(XNode context) 方法,解析 標籤。代碼以下:

// XMLConfigBuilder.java

private void mapperElement(XNode parent) throws Exception { if (parent != null) { // <0> 遍歷子節點 for (XNode child : parent.getChildren()) { // <1> 若是是 package 標籤,則掃描該包 if ("package".equals(child.getName())) { // 得到包名 String mapperPackage = child.getStringAttribute("name"); // 添加到 configuration 中 configuration.addMappers(mapperPackage); // 若是是 mapper 標籤, } else { // 得到 resource、url、class 屬性 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // <2> 使用相對於類路徑的資源引用 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); // 得到 resource 的 InputStream 對象 InputStream inputStream = Resources.getResourceAsStream(resource); // 建立 XMLMapperBuilder 對象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 執行解析 mapperParser.parse(); // <3> 使用徹底限定資源定位符(URL) } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 得到 url 的 InputStream 對象 InputStream inputStream = Resources.getUrlAsStream(url); // 建立 XMLMapperBuilder 對象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); // 執行解析 mapperParser.parse(); // <4> 使用映射器接口實現類的徹底限定類名 } else if (resource == null && url == null && mapperClass != null) { // 得到 Mapper 接口 Class<?> mapperInterface = Resources.classForName(mapperClass); // 添加到 configuration 中 configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } } <0> 處,遍歷子節點,處理每個節點。根據節點狀況,會分紅 <1>、<2>、<3>、<4> 種狀況,而且第一個是處理 標籤,後三個是處理 標籤。 <1> 處,若是是 標籤,則得到 name 報名,並調用 Configuration#addMappers(String packageName) 方法,掃描該包下的全部 Mapper 接口。代碼以下:

// Configuration.java

/**

  • MapperRegistry 對象 */ protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

public void addMappers(String packageName) { // 掃描該包下全部的 Mapper 接口,並添加到 mapperRegistry 中 mapperRegistry.addMappers(packageName); } <4> 處,若是是 mapperClass 非空,則是使用映射器接口實現類的徹底限定類名,則得到 Mapper 接口,並調用 Configuration#addMapper(Class type) 方法,直接添加到 configuration 中。代碼以下:

// Configuration.java

public void addMapper(Class type) { mapperRegistry.addMapper(type); } 實際上,<1> 和 <4> 是類似狀況,差異在於前者須要掃描,才能獲取到全部的 Mapper 接口,然後者明確知道是哪一個 Mapper 接口。 <2> 處,若是是 resource 非空,則是使用相對於類路徑的資源引用,則須要建立 XMLMapperBuilder 對象,並調用 XMLMapperBuilder#parse() 方法,執行解析 Mapper XML 配置。執行以後,咱們就能知道這個 Mapper XML 配置對應的 Mapper 接口。關於 XMLMapperBuilder 類,咱們放在下一篇博客中,詳細解析。 <3> 處,若是是 url 非空,則是使用徹底限定資源定位符(URL),狀況和 <2> 是相似的。

相關文章
相關標籤/搜索