Mybatis源碼解讀-初始化過(guò)程詳解

在使用Mybatis時(shí),我們通常將其配置在Spring容器中,當(dāng)Spring啟動(dòng)的時(shí)候會(huì)自動(dòng)加載Mybatis的所有配置文件然后生成注入到Spring中的Bean,本文從實(shí)用的角度進(jìn)行Mybatis源碼解讀敏沉,會(huì)關(guān)注以下一些方面:

  • Mybatis都有哪些配置文件和配置項(xiàng)
  • Mybatis初始化的源碼流程;
  • Mybatis初始化后攒菠,產(chǎn)生了哪些對(duì)象辖众;

Mybatis初始化環(huán)境并且執(zhí)行SQL語(yǔ)句的JAVA代碼

先看一段初始化Mybatis環(huán)境并且執(zhí)行SQL語(yǔ)句的Java代碼:

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 {
        // 開(kāi)始初始化
        final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
        final Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        // 開(kāi)始執(zhí)行SQL
        SqlSession session = sqlMapper.openSession();
        Integer count = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectCountOfPosts");
        System.out.println(count);
    }
}

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

  1. 讀取Mybatis的配置文件
  2. 構(gòu)建SqlSessionFactory
  3. 從SqlSessionFactory中創(chuàng)建一個(gè)SqlSession
  4. 使用SqlSession執(zhí)行一個(gè)select語(yǔ)句啤它,參數(shù)是Mapper.java的一個(gè)方法名
  5. 打印結(jié)果

在這里前三行代碼包括讀取配置文件和創(chuàng)建SqlSessionFactory离赫,這就是Mybatis的一次初始化過(guò)程。

如果查看一下Spring配置Mybatis的文件蹬刷,就會(huì)發(fā)現(xiàn)它使用mybatis-spring的包也主要是初始化了這個(gè)SqlSessionFactory對(duì)象:

<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接收了三個(gè)參數(shù)搂漠,分別是數(shù)據(jù)源dataSource而克、Mybatis的主配置文件MapperConfig.xml、mapper.xml文件的掃描路徑碎绎。

可以看出Mybatis的初始化過(guò)程就是讀取配置文件然后構(gòu)建出sqlSessionFactory的過(guò)程。

Mybatis都有哪些配置文件和配置項(xiàng)?

上面的Java代碼中初始化Mybatis只使用了配置文件MapperConfig.xml赘淮,然而在Spring配置文件中構(gòu)建sqlSessionFactory時(shí)也使用了mapper.xml配置文件,其實(shí)Mybatis最多也就這兩類文件蛤高,主配置文件MapperConfig.xml可以通過(guò)<mappers>XML元素包含普通的mapper.xml配置文件。

主配置文件:MapperConfig.xml
一個(gè)包含了所有屬性的MapperConfig.xml實(shí)例:

<?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>

主配置文件只有一個(gè)XML節(jié)點(diǎn),就是configuration,它包含9種配置項(xiàng):

  1. properties 屬性:在這里配置的屬性可以在整個(gè)配置文件中使用來(lái)替換需要?jiǎng)討B(tài)配置的屬性值
  2. settings 設(shè)置:MyBatis 中極為重要的調(diào)整設(shè)置雷猪,它們會(huì)改變 MyBatis 的運(yùn)行時(shí)行為,比如是否使用緩存和日志記錄的方式
  3. typeAliases 類型命名:類型別名是為 Java 類型設(shè)置一個(gè)短的名字与境。它只和 XML 配置有關(guān)距误,存在的意義僅在于用來(lái)減少類完全限定名的冗余。
  4. typeHandlers 類型處理器:無(wú)論是 MyBatis 在預(yù)處理語(yǔ)句(PreparedStatement)中設(shè)置一個(gè)參數(shù)時(shí),還是從結(jié)果集中取出一個(gè)值時(shí)泼掠, 都會(huì)用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成 Java 類型括改。
  5. objectFactory 對(duì)象工廠:MyBatis 每次創(chuàng)建結(jié)果對(duì)象的新實(shí)例時(shí)虱疏,它都會(huì)使用一個(gè)對(duì)象工廠(ObjectFactory)實(shí)例來(lái)完成。
  6. plugins 插件:MyBatis 允許你在已映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用,比如增加分頁(yè)功能矛物、格式化輸出最終的SQL等擴(kuò)展;
  7. environments 環(huán)境:MyBatis 可以配置成適應(yīng)多種環(huán)境忆首,這種機(jī)制有助于將 SQL 映射應(yīng)用于多種數(shù)據(jù)庫(kù)之中,比如設(shè)置不同的開(kāi)發(fā)、測(cè)試、線上配置剥险,在每個(gè)配置中可以配置事務(wù)管理器和數(shù)據(jù)源對(duì)象聪蘸;
  8. databaseIdProvider 數(shù)據(jù)庫(kù)廠商標(biāo)識(shí):MyBatis 可以根據(jù)不同的數(shù)據(jù)庫(kù)廠商執(zhí)行不同的語(yǔ)句,這種多廠商的支持是基于映射語(yǔ)句中的 databaseId 屬性表制。
  9. mappers 映射器:MyBatis 的行為已經(jīng)由上述元素配置完了健爬,通過(guò)這里配置的mappers文件,我們可以去查找定義 的SQL 映射語(yǔ)句用于執(zhí)行么介;

可以看出娜遵,前8個(gè)配置項(xiàng)用戶設(shè)定Mybatis運(yùn)行的一些環(huán)境躲雅,而第9個(gè)mappers映射器才是需要執(zhí)行的SQL的配置,在正常情況下,我們只需要配置第9個(gè)mapper映射器的地址即可可免,前面的的Mybatis行為配置都有默認(rèn)值正常情況下不需要設(shè)定修己。

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

雖然我們經(jīng)常寫mapper文件阳距,知道有select/insert/update/delete四種元素馍管,還有sql/resultmap等配置項(xiàng),就感覺(jué)配置項(xiàng)好多好多屎暇,但其實(shí)mapper總共也就8種配置沃饶,我們常用的6種就包含在內(nèi):

  1. cache – 給定命名空間的緩存配置蟋座。
  2. cache-ref – 其他命名空間緩存配置的引用。
  3. resultMap – 是最復(fù)雜也是最強(qiáng)大的元素译红,用于實(shí)現(xiàn)數(shù)據(jù)庫(kù)表列和Java Bean的屬性名的映射配置讳癌;
  4. sql – 可被其他語(yǔ)句引用的可重用語(yǔ)句塊伦腐。
  5. insert – 映射插入語(yǔ)句
  6. update – 映射更新語(yǔ)句
  7. delete – 映射刪除語(yǔ)句
  8. select – 映射查詢語(yǔ)句

正常情況下赏胚,我們很少使用Mybatis提供的cache機(jī)制而是使用外部的Redis等緩存,所以這里的1和2的cache配置幾乎不會(huì)使用,主要也就是我們平時(shí)使用的6種配置。

以上就是Mybatis所有提供給我們配置的地方,改變Mybatis行為的有8個(gè)配置項(xiàng)沸毁,每個(gè)XML配置文件剛好也最多有8個(gè)配置項(xiàng)并级,總共有16個(gè)配置項(xiàng)巴柿。

Mybatis初始化的源碼流程

閱讀Mybatis源碼最好的方式,就是從源碼中的單測(cè)作為入口死遭,然后DEBUG一步步的執(zhí)行,在自己關(guān)注的地方多多停留一會(huì)仔細(xì)查看凯旋。

以下以代碼的流程進(jìn)行解析呀潭,只貼出主要的代碼塊:

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

這里看到,進(jìn)入了new SqlSessionFactoryBuilder().build(reader)方法至非。

進(jìn)入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塊內(nèi)钠署,第一行的內(nèi)容是調(diào)用XPathParser加載了Mybatis的主配置文件,而第二步包含兩個(gè)步驟荒椭,parser.parse()方法返回的是一個(gè)Configuration對(duì)象谐鼎,包裹它的build方法只有一行代碼:

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

這就可以看出,其實(shí)初始化過(guò)程就是創(chuàng)建Configuration對(duì)象的過(guò)程趣惠,對(duì)照MapperConfig.xml的根元素是<configuration>狸棍,不難猜測(cè)到Configuration是一個(gè)非常重要的、包含了Mybatis所有數(shù)據(jù)配置的對(duì)象味悄。

Mybatis核心對(duì)象Configuration的構(gòu)建過(guò)程

接下來(lái)進(jìn)入了XMLConfigBuilder.parse()方法草戈,該方法解析XML文件的/configuration節(jié)點(diǎn),然后挨個(gè)解析了上面配置文件中提到的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);
    }
}

我們挨個(gè)查看侍瑟,這些配置項(xiàng)的解析唐片,都產(chǎn)出了什么內(nèi)容;

1涨颜、properties配置項(xiàng)的解析

進(jìn)入propertiesElement方法费韭,我們發(fā)現(xiàn)初始化了一個(gè)Properties對(duì)象,將XML中所有的子節(jié)點(diǎn)按照KEY-VALUE存入properties之后庭瑰,和Configuration.variables變量進(jìn)行了合并星持,而Configuration.variables本身,也是個(gè)Properties對(duì)象弹灭;

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之后钉汗,后續(xù)的配置文件都可以使用這些變量。

2鲤屡、setting的配置讀取

setting配置的讀取损痰,包含兩個(gè)步驟,第一步酒来,將XML中所有的配置讀取到properties對(duì)象:

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

這個(gè)函數(shù)讀取了setting的配置項(xiàng)卢未,通過(guò)反射訪問(wèn)Configuration.class,如果不存在某個(gè)配置項(xiàng)的set方法則報(bào)錯(cuò);

然后在settingsElement方法中辽社,將這些讀取的配置項(xiàng)存入了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")));
}

因?yàn)閟etting變量直接改變的是Mybatis的行為伟墙,所以配置項(xiàng)直接存于Confirguration的屬性中。

3滴铅、typeAliases配置的解析

進(jìn)入typeAliasesElement方法戳葵,用于對(duì)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的配置項(xiàng)提取之后,存入了typeAliasRegistry這個(gè)對(duì)象汉匙,該對(duì)象是在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類中拱烁,我們看到了該對(duì)象的聲明:

protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

打開(kāi)該類的代碼似嗤,發(fā)現(xiàn)特別簡(jiǎn)單的漓滔,用一個(gè)MAP存儲(chǔ)了別名和對(duì)應(yīng)的類的映射:

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

在構(gòu)造函數(shù)中Mybatis已經(jīng)默認(rèn)注冊(cè)了一些常用的別名和類的關(guān)系,所以我們可以在mappers的xml文件中使用這些短名字邓嘹。

4伤锚、typeHandlers配置元素的解析

mybatis提供了大部分?jǐn)?shù)據(jù)類型的typeHandlers擅笔,如果我們要定制自己的類型處理器比如實(shí)現(xiàn)數(shù)據(jù)庫(kù)中0/1兩個(gè)數(shù)字到中文男/女的映射,就可以自己實(shí)現(xiàn)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);
                }
            }
        }
    }
}

在該方法中屯援,通過(guò)反射得到了javaTypeClass猛们、jdbcType、typeHandlerClass三個(gè)變量狞洋,這三個(gè)變量組成(javaType阅懦、jdbcType、typeHandler)三元組徘铝,當(dāng)遇到j(luò)avaType到j(luò)dbcType的轉(zhuǎn)換耳胎,或者遇到j(luò)dbcType到j(luò)avaType的轉(zhuǎn)換時(shí)就會(huì)使用該typeHandler。

然后該方法調(diào)用了TypeHandlerRegistry.register進(jìn)行注冊(cè)惕它,TypeHandlerRegistry對(duì)象是從BaseBuilder中的Configuration對(duì)象中獲取的:

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中怕午,建立了幾個(gè)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<?>>();

第一個(gè)是JdbcType為key的map,第二個(gè)是JavaType為key的map淹魄,第三個(gè)是未知的處理器郁惜、最后一個(gè)是包含全部的處理器;

當(dāng)執(zhí)行SQL的時(shí)候甲锡,會(huì)將javaBean的JavaType轉(zhuǎn)換到DB的jdbcType兆蕉,而查詢出來(lái)數(shù)據(jù)的時(shí)候,又需要將jdbcType轉(zhuǎn)換成javaType缤沦,在TypeHandlerRegistry的構(gòu)造函數(shù)中虎韵,已經(jīng)注冊(cè)好了很多默認(rèn)的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());

要實(shí)現(xiàn)一個(gè)typeHandler缸废,需要實(shí)現(xiàn)接口包蓝,該接口提供的就是從javaType到j(luò)dbcType的setParameter方法驶社,以及從jdbcType到j(luò)avaType轉(zhuǎn)換的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;
}

5、objectFactory配置項(xiàng)的解析

如果想自己控制查詢數(shù)據(jù)庫(kù)的結(jié)果到JavaBean映射的生成测萎,則可以創(chuàng)建自己的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);
    }
}

可以看到,該配置項(xiàng)包含type屬性硅瞧,以及properties子節(jié)點(diǎn)份乒,創(chuàng)建好ObjectFactory對(duì)象后,就會(huì)設(shè)置到configuration中:

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

要實(shí)現(xiàn)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);
}

該工廠接口提供了設(shè)置屬性列表或辖,還有創(chuàng)建對(duì)象的工廠方法。

6四苇、plugin元素的解析

plugin,即mybatis的插件方咆,可以讓我們自己進(jìn)行開(kāi)發(fā)用于擴(kuò)展mybatis月腋。

進(jìn)入pluginElement方法進(jìn)入解析:

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元素作為攔截器瓣赂,然后讀取該節(jié)點(diǎn)的所有子節(jié)點(diǎn)作為配置項(xiàng)榆骚,最后調(diào)用configuration.addInterceptor方法添加到了configuration中的interceptorChain中,該對(duì)象是攔截器鏈的一個(gè)包裝對(duì)象:

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>存儲(chǔ)了所有配置的攔截器煌集,并提供了addInterceptor用于添加攔截器妓肢,提供了getInterceptors用于獲取當(dāng)前所有添加的插件列表,提供了pluginAll接口調(diào)用所有的Interceptor.plugin(Object)方法進(jìn)行插件的執(zhí)行苫纤。

7碉钠、environments的配置解析

environments可以配置多個(gè)環(huán)境配置,每個(gè)配置包含了數(shù)據(jù)源和事務(wù)管理器兩項(xiàng)卷拘,如下所示代碼:

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

代碼中通過(guò)isSpecifiedEnvironment方法判斷當(dāng)前的id是不是指定要讀取的environment喊废,如果是的話通過(guò)反射獲取事務(wù)管理器和數(shù)據(jù)源,然后用Environment.Builder創(chuàng)建Enviroment對(duì)象并設(shè)置到Configuration中栗弟,在Configuration中可以看到Enviroment成員變量:

protected Environment environment;

而Enviroment對(duì)象也只包含了這三個(gè)屬性:

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

8污筷、databaseIdProvider配置項(xiàng)的解析

mybatis當(dāng)然不只是支持mysql,也會(huì)支持oracle乍赫、sqlserver等不同的數(shù)據(jù)庫(kù)瓣蛀,解析代碼如下:

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中存儲(chǔ)了各種數(shù)據(jù)庫(kù)的映射,并且databaseIdProvider提供了一個(gè)根據(jù)dataSource獲取對(duì)應(yīng)的databseId的方法雷厂,以VendorDatabaseIdProvider為例惋增,是通過(guò)connection.getMetaData().getDatabaseProductName()獲取數(shù)據(jù)庫(kù)的產(chǎn)品名稱,然后從剛才databaseIdProvider中獲取對(duì)應(yīng)的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設(shè)置到configuration器腋,后續(xù)當(dāng)執(zhí)行SQL的時(shí)候會(huì)自動(dòng)根據(jù)該databaseId來(lái)映射具體數(shù)據(jù)庫(kù)的SQL。

9、mappers配置項(xiàng)的解析

mappers的解析最為復(fù)雜纫塌,我們假設(shè)mapper文件均是url指定的xml文件诊县,來(lái)進(jìn)行解析流程的查看:

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,然后進(jìn)入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.");
                }
            }
        }
    }
}

加注釋部分顯示措左,讀取每個(gè)mapper.xml資源文件的地址后依痊,進(jìn)入了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);
    }
}

然后進(jìn)入了configurationElement(parser.evalNode(“/mapper”));方法后會(huì)讀取所有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);
        }
    }
}

該方法出現(xiàn)了一個(gè)XMLStatementBuilder用于select/insert/update/delete語(yǔ)句各自的解析怎披,在XMLStatementBuilder.parseStatementNode方法中解析了各種語(yǔ)句的屬性和參數(shù)以及動(dòng)態(tài)SQL的處理胸嘁,最后調(diào)用builderAssistant.addMappedStatement方法,所有的參數(shù)和內(nèi)容被構(gòu)建成MappedStatement凉逛,添加到了configuration中:

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

以下是configuration中的mappedStatements對(duì)象:

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

這里的StrictMap就是一個(gè)HashMap性宏,在addMappedStatement方法中可以看到該map的Key是各個(gè)SQL的ID:

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

由此可以推測(cè)整個(gè)Mybatis執(zhí)行SQL的過(guò)程:

  1. 業(yè)務(wù)代碼調(diào)用SqlMapperInterface.method方法;
  2. Mybatis根據(jù)method的全限定名稱作為ID状飞,從mappedStatements找到構(gòu)建好的MappedStatement對(duì)象毫胜;
  3. 使用MappedStatement中讀取的SQL等各種配置,執(zhí)行SQL诬辈;

Mybatis初始化的對(duì)象產(chǎn)出列表總結(jié)

以上就是對(duì)Mybatis初始化過(guò)程的詳解酵使,其最終產(chǎn)出了以下對(duì)象列表:

  1. 總產(chǎn)出:SqlSessionFactory,相當(dāng)于ConnectionFactory用于生產(chǎn)SqlSession焙糟,而SqlSession相當(dāng)于Connection用于實(shí)際的SQL查詢口渔;

  2. SQlSessionFactory的核心對(duì)象Configuration,所有的Mybatis配置項(xiàng)和Mapper配置列表穿撮,都會(huì)被解析并讀取到該對(duì)象的屬性中缺脉;

  3. Configuration中的各個(gè)對(duì)象:

    • Configuration.variables,類型為Properties悦穿,存儲(chǔ)Mybatis配置的Properties對(duì)象枪向;
    • Configuration.直接屬性,setting的配置咧党,因?yàn)橹苯佑绊慚ybatis的行為秘蛔,直接賦值到了Configuration對(duì)象的屬性;
    • Configuration.TypeAliasRegistry對(duì)象傍衡,存儲(chǔ)別名映射深员,KEY是別名,VALUE是別名對(duì)應(yīng)的類蛙埂;
    • Configuration.TypeHandlerRegistry對(duì)象倦畅,存儲(chǔ)typeHandler映射,KEY是javaType或者jdbcType绣的,VALUE是typeHandler類叠赐;
    • Configuration.ObjectFactory對(duì)象欲账,存儲(chǔ)單個(gè)的ObjectFactory對(duì)象,用于DB映射JAVABEAN對(duì)象的創(chuàng)建芭概;
    • Configuration.InterceptorChain對(duì)象赛不,存儲(chǔ)Plugin對(duì)象列表,用戶可以添加用于擴(kuò)展Mybatis罢洲;
    • Configuration.Environment對(duì)象踢故,指定開(kāi)發(fā)/測(cè)試/線上環(huán)境殿较,根據(jù)其dataSource也決定了databaseId對(duì)象的取值院究;
    • Configuration.databaseId片任,存儲(chǔ)使用的DB的類型位他,Mybatis會(huì)根據(jù)不同的DB做SQL適配舞竿;
    • Configuration.mappedStatements骗奖,存儲(chǔ)KEY為ID芜赌,VALUE為MappedStatement的MAP膘壶,執(zhí)行SQL時(shí)從此處獲取對(duì)象;

寫在最后

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玛界,一起剝皮案震驚了整個(gè)濱河市笨枯,隨后出現(xiàn)的幾起案子严嗜,更是在濱河造成了極大的恐慌,老刑警劉巖洲敢,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漫玄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡压彭,警方通過(guò)查閱死者的電腦和手機(jī)睦优,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)壮不,“玉大人汗盘,你說(shuō)我怎么就攤上這事⊙唬” “怎么了隐孽?”我有些...
    開(kāi)封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)健蕊。 經(jīng)常有香客問(wèn)我菱阵,道長(zhǎng),這世上最難降的妖魔是什么绊诲? 我笑而不...
    開(kāi)封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任送粱,我火速辦了婚禮,結(jié)果婚禮上掂之,老公的妹妹穿的比我還像新娘抗俄。我一直安慰自己脆丁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布动雹。 她就那樣靜靜地躺著槽卫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胰蝠。 梳的紋絲不亂的頭發(fā)上歼培,一...
    開(kāi)封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音茸塞,去河邊找鬼躲庄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钾虐,可吹牛的內(nèi)容都是我干的噪窘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼效扫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼倔监!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起菌仁,我...
    開(kāi)封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浩习,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后济丘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谱秽,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年闪盔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弯院。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辱士。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泪掀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颂碘,到底是詐尸還是另有隱情异赫,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布头岔,位于F島的核電站塔拳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏峡竣。R本人自食惡果不足惜靠抑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望适掰。 院中可真熱鬧颂碧,春花似錦荠列、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至诉瓦,卻和暖如春川队,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睬澡。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工固额, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人煞聪。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓对雪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親米绕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瑟捣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL栅干、存儲(chǔ)過(guò)程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,523評(píng)論 0 4
  • MyBatis 理論篇 [TOC] 什么是MyBatis ?MyBatis是支持普通SQL查詢,存儲(chǔ)過(guò)程和高級(jí)映射...
    有_味閱讀 2,904評(píng)論 0 26
  • 什么是mybatis MyBatis 是支持定制化 SQL迈套、存儲(chǔ)過(guò)程以及高級(jí)映射的優(yōu)秀的持久層框架。MyBatis...
    seadragonnj閱讀 2,332評(píng)論 0 7
  • 記錄是一種精神碱鳞,是加深理解最好的方式之一桑李。 最近看了下Mybatis的源碼,了解了下Mybatis對(duì)配置文件的解析...
    曹金桂閱讀 7,683評(píng)論 3 35
  • 搭建源碼環(huán)境 在這里我提一下,在早期Mybatis版本中,Dao開(kāi)發(fā)方式都是有Mapper接口和其實(shí)現(xiàn)類的,實(shí)現(xiàn)類...
    十年開(kāi)發(fā)程序員閱讀 420評(píng)論 0 0