天天看點

mybatis源碼系列之 -- 整體流程源碼分析

一方面是增強自己對這個現在非常流行的持久層架構了解,一方面是最近想做一個類似于PageHelper的分頁插件,是以對mybatis的具體工作原理還是要有一定的認識,才能完成這個插件。

核心類:

  • SqlSessionFactoryBuilder 用于生産SqlSessionFactory
  • XMLConfigBuilder 用于解析mybatis的核心配置檔案sqlMapConfig.xml

先看一下mybatis核心配置檔案sqlMapConfig.xml

<?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="db.properties"></properties>

    <settings>
        <!-- 打開延遲加載的開關 -->
        <setting name="lazyLoadingEnabled" value="false" />
        <!-- 将積極加載改為消息加載即按需加載 -->
        <setting name="aggressiveLazyLoading" value="false" />
        <setting name="lazyLoadTriggerMethods" value=""/>
        <setting name="cacheEnabled" value="true" />
        <setting name= "mapUnderscoreToCamelCase" value = "true"/>
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <setting name="useGeneratedKeys" value="true" />
    </settings>

    <typeAliases>
        <package name="top.takefly.pojo"/>
    </typeAliases>


    <!--配租插件-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED" >
                <property name="driver"  value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <environment id="oracle">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
            </dataSource>
        </environment>
    </environments>

    <mappers>
<!--        <mapper class="top.takefly.dao.IUserDao"/>-->
<!--        <mapper resource="top/takefly/dao/IUserDao.xml" />-->
        <package name="top.takefly.dao"/>
    </mappers>
</configuration>
           

第一步

首先要通過SqlSessionBuilder建構出SqlSessionFactory,這是通過工廠模式建構出來的。

工廠模式的好處:

1.将對象的建立交由工廠來維護,隻需要從工廠擷取對象即可。

2.易于功能擴充

3.遵循開閉原則

4.封裝性,對外提供接口,具體實作由工廠來選擇

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

我們來看一下mybatis是如何傳入Configuration對象的

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }
           
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
SqlSessionFactory var5 = this.build(parser.parse());
           

從代碼中我們看到了,Configuration對象是由XMLConfigBuilder的parse方法解析出來的,parser.parse()傳回的是一個Configuration對象,this.build(Configuration config)調用的參數為Configuration的重載構造。

現在具體看一下XMLConfigBuilder是如何通過parse()方法解析出Configuration的

public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
}
           

首先XMLConfigBuilder的parse()會判斷目前XMLConfigBuilder是否被使用過,如果被使用過就會抛出異常,如果沒有使用過就會将parsed辨別為true,同時調用parseConfiguration(XNode root)方法,這個this.parser.evalNode("/configuration")擷取的就是configuration标簽下的所有标簽,接下來看看parseConfiguration(XNode root)方法是如何解析的

private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }
           

解析步驟如下

  1. 擷取properties标簽,判斷是否加載外部配置檔案
  1. 擷取setting标簽中的配置,擷取Settings标簽中配置的屬性
private Properties settingsAsProperties(XNode context) {
        if (context == null) {
            return new Properties();
        } else {
            Properties props = context.getChildrenAsProperties();
            MetaClass metaConfig = MetaClass.forClass(Configuration.class, this.localReflectorFactory);
            Iterator var4 = props.keySet().iterator();

            Object key;
            do {
                if (!var4.hasNext()) {
                    return props;
                }

                key = var4.next();
            } while(metaConfig.hasSetter(String.valueOf(key)));

            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
    }
           

3.加載自定義元件,其實就是添加一個Interceptor

private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String interceptor = child.getStringAttribute("interceptor");
                Properties properties = child.getChildrenAsProperties();
                Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
                interceptorInstance.setProperties(properties);
                this.configuration.addInterceptor(interceptorInstance);
            }
        }

    }
           
  1. 設定settingsElement
private void settingsElement(Properties props) {
        this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
        this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
        this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));
        this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));
        this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
        this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
        this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
        this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));
        this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));
        this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
        this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));
        this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));
        this.configuration.setDefaultResultSetType(this.resolveResultSetType(props.getProperty("defaultResultSetType")));
        this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
        this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
        this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
        this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
        this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
        this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
        this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));
        this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));
        this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));
        this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
        this.configuration.setLogPrefix(props.getProperty("logPrefix"));
        this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));
    }