Mybatis源碼解讀-初始化過程詳解

clipboard.png

在使用Mybatis時,咱們一般將其配置在Spring容器中,當Spring啓動的時候會自動加載Mybatis的全部配置文件而後生成注入到Spring中的Bean,本文從實用的角度進行Mybatis源碼解讀,會關注如下一些方面:java

  • Mybatis都有哪些配置文件和配置項
  • Mybatis初始化的源碼流程;
  • Mybatis初始化後,產生了哪些對象;

Mybatis初始化環境而且執行SQL語句的JAVA代碼

先看一段初始化Mybatis環境而且執行SQL語句的Java代碼:mysql

package org.apache.ibatis.session;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
public class MyTest {
    public static void main(String[] args) throws Exception {
        // 開始初始化
        final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
        final Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        // 開始執行SQL
        SqlSession session = sqlMapper.openSession();
        Integer count = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectCountOfPosts");
        System.out.println(count);
    }
}

這段代碼完成了這些事情:spring

  1. 讀取Mybatis的配置文件
  2. 構建SqlSessionFactory
  3. 從SqlSessionFactory中建立一個SqlSession
  4. 使用SqlSession執行一個select語句,參數是Mapper.java的一個方法名
  5. 打印結果

在這裏前三行代碼包括讀取配置文件和建立SqlSessionFactory,這就是Mybatis的一次初始化過程。sql

若是查看一下Spring配置Mybatis的文件,就會發現它使用mybatis-spring的包也主要是初始化了這個SqlSessionFactory對象:數據庫

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:conf/MapperConfig.xml" />
        <property name="mapperLocations">
            <list>
                <value>classpath*:mapper
/*.xml</value>
            </list>
        </property>
    </bean>

該Spring配置sqlSessionFactory接收了三個參數,分別是數據源dataSource、Mybatis的主配置文件MapperConfig.xml、mapper.xml文件的掃描路徑。apache

能夠看出Mybatis的初始化過程就是讀取配置文件而後構建出sqlSessionFactory的過程。緩存

Mybatis都有哪些配置文件和配置項?

上面的Java代碼中初始化Mybatis只使用了配置文件MapperConfig.xml,然而在Spring配置文件中構建sqlSessionFactory時也使用了mapper.xml配置文件,其實Mybatis最多也就這兩類文件,主配置文件MapperConfig.xml能夠經過<mappers>XML元素包含普通的mapper.xml配置文件。session

主配置文件:MapperConfig.xml
一個包含了全部屬性的MapperConfig.xml實例:mybatis

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="org/apache/ibatis/databases/blog/blog-derby.properties" />
    <settings>
        <setting name="cacheEnabled" value="true" />
        <setting name="lazyLoadingEnabled" value="false" />
        <setting name="multipleResultSetsEnabled" value="true" />
        <setting name="useColumnLabel" value="true" />
        <setting name="useGeneratedKeys" value="false" />
        <setting name="defaultExecutorType" value="SIMPLE" />
        <setting name="defaultStatementTimeout" value="25" />
    </settings>
    <typeAliases>
        <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author" />
        <typeAlias alias="Blog" type="org.apache.ibatis.domain.blog.Blog" />
    </typeAliases>
    <typeHandlers>
        <typeHandler javaType="String" jdbcType="VARCHAR"
            handler="org.apache.ibatis.builder.CustomStringTypeHandler" />
    </typeHandlers>
    <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
        <property name="objectFactoryProperty" value="100" />
    </objectFactory>
    <plugins>
        <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
            <property name="pluginProperty" value="100" />
        </plugin>
    </plugins>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value="" />
            </transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>
    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver" />
        <property name="DB2" value="db2" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>
    <mappers>
        <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml" />
        <mapper resource="org/apache/ibatis/builder/BlogMapper.xml" />
    </mappers>
</configuration>

主配置文件只有一個XML節點,就是configuration,它包含9種配置項:架構

  1. properties 屬性:在這裏配置的屬性能夠在整個配置文件中使用來替換須要動態配置的屬性值
  2. settings 設置:MyBatis 中極爲重要的調整設置,它們會改變 MyBatis 的運行時行爲,好比是否使用緩存和日誌記錄的方式
  3. typeAliases 類型命名:類型別名是爲 Java 類型設置一個短的名字。它只和 XML 配置有關,存在的意義僅在於用來減小類徹底限定名的冗餘。
  4. typeHandlers 類型處理器:不管是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,仍是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。
  5. objectFactory 對象工廠:MyBatis 每次建立結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成。
  6. plugins 插件:MyBatis 容許你在已映射語句執行過程當中的某一點進行攔截調用,好比增長分頁功能、格式化輸出最終的SQL等擴展;
  7. environments 環境:MyBatis 能夠配置成適應多種環境,這種機制有助於將 SQL 映射應用於多種數據庫之中,好比設置不一樣的開發、測試、線上配置,在每一個配置中能夠配置事務管理器和數據源對象;
  8. databaseIdProvider 數據庫廠商標識:MyBatis 能夠根據不一樣的數據庫廠商執行不一樣的語句,這種多廠商的支持是基於映射語句中的 databaseId 屬性。
  9. mappers 映射器:MyBatis 的行爲已經由上述元素配置完了,經過這裏配置的mappers文件,咱們能夠去查找定義 的SQL 映射語句用於執行;

能夠看出,前8個配置項用戶設定Mybatis運行的一些環境,而第9個mappers映射器纔是須要執行的SQL的配置,在正常狀況下,咱們只須要配置第9個mapper映射器的地址便可,前面的的Mybatis行爲配置都有默認值正常狀況下不須要設定。

包含最終SQL的mapper映射器配置文件

雖然咱們常常寫mapper文件,知道有select/insert/update/delete四種元素,還有sql/resultmap等配置項,就感受配置項好多好多,但其實mapper總共也就8種配置,咱們經常使用的6種就包含在內:

  1. cache – 給定命名空間的緩存配置。
  2. cache-ref – 其餘命名空間緩存配置的引用。
  3. resultMap – 是最複雜也是最強大的元素,用於實現數據庫表列和Java Bean的屬性名的映射配置;
  4. sql – 可被其餘語句引用的可重用語句塊。
  5. insert – 映射插入語句
  6. update – 映射更新語句
  7. delete – 映射刪除語句
  8. select – 映射查詢語句

正常狀況下,咱們不多使用Mybatis提供的cache機制而是使用外部的Redis等緩存,因此這裏的1和2的cache配置幾乎不會使用,主要也就是咱們平時使用的6種配置。

以上就是Mybatis全部提供給咱們配置的地方,改變Mybatis行爲的有8個配置項,每一個XML配置文件恰好也最多有8個配置項,總共有16個配置項。

Mybatis初始化的源碼流程

閱讀Mybatis源碼最好的方式,就是從源碼中的單測做爲入口,而後DEBUG一步步的執行,在本身關注的地方多多停留一會仔細查看。

如下以代碼的流程進行解析,只貼出主要的代碼塊:

Mybatis代碼初始化入口

@BeforeClass
    public static void setup() throws Exception {
    createBlogDataSource();
    final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
    final Reader reader = Resources.getResourceAsReader(resource);
    sqlMapper = new SqlSessionFactoryBuilder().build(reader);
}

這裏看到,進入了new SqlSessionFactoryBuilder().build(reader)方法。

進入SqlSessionFactoryBuilder的build方法

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        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.
        }
    }
}

主要兩行在try塊內,第一行的內容是調用XPathParser加載了Mybatis的主配置文件,而第二步包含兩個步驟,parser.parse()方法返回的是一個Configuration對象,包裹它的build方法只有一行代碼:

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

這就能夠看出,其實初始化過程就是建立Configuration對象的過程,對照MapperConfig.xml的根元素是<configuration>,不難猜想到Configuration是一個很是重要的、包含了Mybatis全部數據配置的對象。

Mybatis核心對象Configuration的構建過程

接下來進入了XMLConfigBuilder.parse()方法,該方法解析XML文件的/configuration節點,而後挨個解析了上面配置文件中提到的9大配置:

public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return 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);
    }
}

咱們挨個查看,這些配置項的解析,都產出了什麼內容;

一、properties配置項的解析

進入propertiesElement方法,咱們發現初始化了一個Properties對象,將XML中全部的子節點按照KEY-VALUE存入properties以後,和Configuration.variables變量進行了合併,而Configuration.variables自己,也是個Properties對象;

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        Properties defaults = context.getChildrenAsProperties();
        String resource = context.getStringAttribute("resource");
        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);
        }
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

將properties配置解析後合併到Configuration.variables以後,後續的配置文件均可以使用這些變量。

二、setting的配置讀取

setting配置的讀取,包含兩個步驟,第一步,將XML中全部的配置讀取到properties對象:

private Properties settingsAsProperties(XNode context) {
    if (context == null) {
        return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    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;
}

這個函數讀取了setting的配置項,經過反射訪問Configuration.class,若是不存在某個配置項的set方法則報錯;

而後在settingsElement方法中,將這些讀取的配置項存入了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")));
    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")));
}

由於setting變量直接改變的是Mybatis的行爲,因此配置項直接存於Confirguration的屬性中。

三、typeAliases配置的解析

進入typeAliasesElement方法,用於對typeAliases配置的解析:

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

該方法將typeAliases的配置項提取以後,存入了typeAliasRegistry這個對象,該對象是在BaseBuilder中初始化的:

public abstract class BaseBuilder {
    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類中,咱們看到了該對象的聲明:

protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

打開該類的代碼,發現特別簡單的,用一個MAP存儲了別名和對應的類的映射:

public class TypeAliasRegistry {
    private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
    public TypeAliasRegistry() {
        registerAlias("string", String.class);
        registerAlias("byte", byte.class);
        registerAlias("long", long.class);
        registerAlias("short", short.class);
        registerAlias("int", Integer.class);
        registerAlias("integer", Integer.class);
        registerAlias("double", double.class);
        registerAlias("float", float.class);
        registerAlias("boolean", Boolean.class);

在構造函數中Mybatis已經默認註冊了一些經常使用的別名和類的關係,因此咱們能夠在mappers的xml文件中使用這些短名字。

四、typeHandlers配置元素的解析

mybatis提供了大部分數據類型的typeHandlers,若是咱們要定製本身的類型處理器好比實現數據庫中0/1兩個數字到中文男/女的映射,就能夠本身實現typeHandler

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

在該方法中,經過反射獲得了javaTypeClass、jdbcType、typeHandlerClass三個變量,這三個變量組成(javaType、jdbcType、typeHandler)三元組,當遇到javaType到jdbcType的轉換,或者遇到jdbcType到javaType的轉換時就會使用該typeHandler。

而後該方法調用了TypeHandlerRegistry.register進行註冊,TypeHandlerRegistry對象是從BaseBuilder中的Configuration對象中獲取的:

public abstract class BaseBuilder {
    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();
    }

在TypeHandlerRegistry中,創建了幾個Map映射:

public final class TypeHandlerRegistry {
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(
                JdbcType.class);
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<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<?>>();

第一個是JdbcType爲key的map,第二個是JavaType爲key的map,第三個是未知的處理器、最後一個是包含所有的處理器;

當執行SQL的時候,會將javaBean的JavaType轉換到DB的jdbcType,而查詢出來數據的時候,又須要將jdbcType轉換成javaType,在TypeHandlerRegistry的構造函數中,已經註冊好了不少默認的typeHandler,大部分狀況下不須要咱們添加:

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());

要實現一個typeHandler,須要實現接口,該接口提供的就是從javaType到jdbcType的setParameter方法,以及從jdbcType到javaType轉換的getResult方法:

public interface TypeHandler<T> {
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    T getResult(ResultSet rs, String columnName) throws SQLException;
    T getResult(ResultSet rs, int columnIndex) throws SQLException;
    T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

五、objectFactory配置項的解析

若是想本身控制查詢數據庫的結果到JavaBean映射的生成,則能夠建立本身的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);
    }
}

能夠看到,該配置項包含type屬性,以及properties子節點,建立好ObjectFactory對象後,就會設置到configuration中:

// Configuration對象的objectFactory成員變量
protected ObjectFactory objectFactory = new DefaultObjectFactory();

要實現ObjectFactory,須要繼承該接口:

public interface ObjectFactory {
    void setProperties(Properties properties);
    <T> T create(Class<T> type);
    <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
    <T> boolean isCollection(Class<T> type);
}

該工廠接口提供了設置屬性列表,還有建立對象的工廠方法。

六、plugin元素的解析

plugin,即mybatis的插件,可讓咱們本身進行開發用於擴展mybatis。

進入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 interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

該段代碼,首先獲取intercepter元素做爲攔截器,而後讀取該節點的全部子節點做爲配置項,最後調用configuration.addInterceptor方法添加到了configuration中的interceptorChain中,該對象是攔截器鏈的一個包裝對象:

public class InterceptorChain {
    private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            // target變量每次也在變化着
            target = interceptor.plugin(target);
        }
        return target;
    }
    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }
}

該類中使用List<Interceptor>存儲了全部配置的攔截器,並提供了addInterceptor用於添加攔截器,提供了getInterceptors用於獲取當前全部添加的插件列表,提供了pluginAll接口調用全部的Interceptor.plugin(Object)方法進行插件的執行。

七、environments的配置解析

environments能夠配置多個環境配置,每一個配置包含了數據源和事務管理器兩項,以下所示代碼:

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");
            if (isSpecifiedEnvironment(id)) {
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory)
                                            .dataSource(dataSource);
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

代碼中經過isSpecifiedEnvironment方法判斷當前的id是否是指定要讀取的environment,若是是的話經過反射獲取事務管理器和數據源,而後用Environment.Builder建立Enviroment對象並設置到Configuration中,在Configuration中能夠看到Enviroment成員變量:

protected Environment environment;

而Enviroment對象也只包含了這三個屬性:

public final class Environment {
    private final String id;
    private final TransactionFactory transactionFactory;
    private final DataSource dataSource;

八、databaseIdProvider配置項的解析

mybatis固然不僅是支持mysql,也會支持oracle、sqlserver等不一樣的數據庫,解析代碼以下:

private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        String type = context.getStringAttribute("type");
        // awful patch to keep backward compatibility
        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后里面的Properties中存儲了各類數據庫的映射,而且databaseIdProvider提供了一個根據dataSource獲取對應的databseId的方法,以VendorDatabaseIdProvider爲例,是經過connection.getMetaData().getDatabaseProductName()獲取數據庫的產品名稱,而後從剛纔databaseIdProvider中獲取對應的databaseId:

public class VendorDatabaseIdProvider implements DatabaseIdProvider {
    private static final Log log = LogFactory.getLog(VendorDatabaseIdProvider.class);
    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;
    }
    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
                }
            }
        }
    }
}

在獲取了databaseId以後,最後將databaseId設置到configuration,後續當執行SQL的時候會自動根據該databaseId來映射具體數據庫的SQL。

九、mappers配置項的解析

mappers的解析最爲複雜,咱們假設mapper文件均是url指定的xml文件,來進行解析流程的查看:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 使用XMLMapperBuilder加載mapper.xml,而後進入parse()方法
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
                                                    configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
                                                    configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException(
                                                    "A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

加註釋部分顯示,讀取每一個mapper.xml資源文件的地址後,進入了XMLMapperBuilder.parse()方法:

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}
private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    }
    catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
}

而後進入了configurationElement(parser.evalNode(「/mapper」));方法後會讀取全部xml中mapper下的子元素,在這裏咱們只查看buildStatementFromContext方法:

private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant,
                            context, requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        }
        catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

該方法出現了一個XMLStatementBuilder用於select/insert/update/delete語句各自的解析,在XMLStatementBuilder.parseStatementNode方法中解析了各類語句的屬性和參數以及動態SQL的處理,最後調用builderAssistant.addMappedStatement方法,全部的參數和內容被構建成MappedStatement,添加到了configuration中:

MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);

如下是configuration中的mappedStatements對象:

// Configuration的mappedStatements對象
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
            "Mapped Statements collection");

這裏的StrictMap就是一個HashMap,在addMappedStatement方法中能夠看到該map的Key是各個SQL的ID:

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
}

由此能夠推測整個Mybatis執行SQL的過程:

  1. 業務代碼調用SqlMapperInterface.method方法;
  2. Mybatis根據method的全限定名稱做爲ID,從mappedStatements找到構建好的MappedStatement對象;
  3. 使用MappedStatement中讀取的SQL等各類配置,執行SQL;

Mybatis初始化的對象產出列表總結

以上就是對Mybatis初始化過程的詳解,其最終產出瞭如下對象列表:

  1. 總產出:SqlSessionFactory,至關於ConnectionFactory用於生產SqlSession,而SqlSession至關於Connection用於實際的SQL查詢;
  2. SQlSessionFactory的核心對象Configuration,全部的Mybatis配置項和Mapper配置列表,都會被解析並讀取到該對象的屬性中;
  3. Configuration中的各個對象:

    • Configuration.variables,類型爲Properties,存儲Mybatis配置的Properties對象;
    • Configuration.直接屬性,setting的配置,由於直接影響Mybatis的行爲,直接賦值到了Configuration對象的屬性;
    • Configuration.TypeAliasRegistry對象,存儲別名映射,KEY是別名,VALUE是別名對應的類;
    • Configuration.TypeHandlerRegistry對象,存儲typeHandler映射,KEY是javaType或者jdbcType,VALUE是typeHandler類;
    • Configuration.ObjectFactory對象,存儲單個的ObjectFactory對象,用於DB映射JAVABEAN對象的建立;
    • Configuration.InterceptorChain對象,存儲Plugin對象列表,用戶能夠添加用於擴展Mybatis;
    • Configuration.Environment對象,指定開發/測試/線上環境,根據其dataSource也決定了databaseId對象的取值;
    • Configuration.databaseId,存儲使用的DB的類型,Mybatis會根據不一樣的DB作SQL適配;
    • Configuration.mappedStatements,存儲KEY爲ID,VALUE爲MappedStatement的MAP,執行SQL時今後處獲取對象;

寫在最後

最後,歡迎作Java的工程師朋友們加入Java高級架構進階Qqun:963944895

羣內有技術大咖指點難題,還提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)

比你優秀的對手在學習,你的仇人在磨刀,你的閨蜜在減肥,隔壁老王在練腰, 咱們必須不斷學習,不然咱們將被學習者超越!

趁年輕,使勁拼,給將來的本身一個交代!

clipboard.png

相關文章
相關標籤/搜索