Mybatis源碼閱讀(三)SqlSession的創(chuàng)建和運(yùn)行

接上一篇文章SqlSessionFactory的創(chuàng)建
http://www.reibang.com/p/eb3d06a7c77d

SqlSession的創(chuàng)建過程

既然已經(jīng)得到了SqlSessionFactory览露,那么SqlSession將由SqlSessionFactory進(jìn)行創(chuàng)建截歉。

SqlSession sqlSession=sqlSessionFactory.openSession();

這樣脐区,我們就來看看這個(gè)SqlSessionFactoryopenSession方法是如何創(chuàng)建SqlSession對象的。根據(jù)上面的分析昭抒,這里的SqlSessionFactory類型對象其實(shí)是一個(gè)DefaultSqlSessionFactory對象攘蔽,因此宋下,需要到DefaultSqlSessionFactory類中去看openSession方法。

// DefaultSqlSessionFactory 類

@Override
public SqlSession openSession() {
    return openSessionFromDataSource(
        configuration.getDefaultExecutorType(), null, false);
}

/**
 * 這里對參數(shù)類型進(jìn)行說明
 * ExecutorType 指定Executor的類型篡腌,分為三種:SIMPLE, REUSE, BATCH
 * TransactionIsolationLevel 指定事務(wù)隔離級別
 * 使用null,則表示使用數(shù)據(jù)庫默認(rèn)的事務(wù)隔離界別
 * autoCommit 是否自動(dòng)提交
 */
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 獲取配置中的環(huán)境信息,包括了數(shù)據(jù)源信息勾效、事務(wù)等
        final Environment environment = configuration.getEnvironment();
        // 創(chuàng)建事務(wù)工廠
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 創(chuàng)建事務(wù)哀蘑,配置事務(wù)屬性
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 創(chuàng)建Executor,即執(zhí)行器
        // 它是真正用來Java和數(shù)據(jù)庫交互操作的類葵第,后面會(huì)展開說绘迁。
        final Executor executor = configuration.newExecutor(tx, execType);
        // 創(chuàng)建DefaultSqlSession對象返回,因?yàn)镾qlSession是一個(gè)接口
        // 可以類比DefaultSqlSessionFactory
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

這樣我們就得到了DefaultSqlSession(SqlSession)卒密∽禾ǎ可以看到基本覆蓋數(shù)據(jù)庫的各種操作,增刪查改哮奇,以及簡單的事務(wù)的操作膛腐。

image-20181028144109252

接下來就要看看它的執(zhí)行過程。

SqlSession的執(zhí)行過程

獲取到了SqlSession之后鼎俘,則需要執(zhí)行下面的語句

CountryMapper countryMapper=
    sqlSession.getMapper(CountryMapper.class);

因此我們要看看getMapper這個(gè)方法干了什么哲身。上面的分析知道,這里的sqlSession其實(shí)是DefaultSqlSession對象贸伐,因此需要在DefaultSqlSession中去查看這個(gè)方法勘天。

// DefaultSqlSession 類
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
}

這里的configurationConfiguration類的實(shí)例,在SqlSessionFactory中創(chuàng)建并完成所有配置的解析后捉邢,初始化DefaultSqlSession時(shí)脯丝,SqlSessionFactory將配置作為屬性傳給DefaultSqlSession,因此前面解析的所有配置伏伐,都能在這里查到宠进。

因此我們繼續(xù)往下看,這里就要定位到Configuration類的getMapper方法了藐翎。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // mapperRegistry 是一個(gè)mapper配置的容器材蹬,前面有提到
    // 配置路徑下所有掃描到的mapper在初始化完成Configuration以后,都會(huì)加載進(jìn)來
    // 每一個(gè)mapper都被存儲(chǔ)在了MapperRegistry的knownMappers中了
    // 在初始化配置的時(shí)候執(zhí)行addMapper吝镣,在獲取Mapper的時(shí)候執(zhí)行g(shù)etMapper
    return mapperRegistry.getMapper(type, sqlSession);
}

因此堤器,我們就要來看看MapperRegistrygetMapper方法

// MapperRegistry的getMapper方法
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 從knownMappers集合中獲取mapper,創(chuàng)建MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 獲取代理對象,并返回
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

看到了MapperProxyFactory很明顯這是一個(gè)工廠類赤惊,所以肯定會(huì)有MapperProxy這么一個(gè)類吼旧,而看到Proxy,這里肯定用到了代理未舟,也肯定就是動(dòng)態(tài)代理了圈暗。

我們來看看獲取代理對象的方法 newInstance

// MapperProxyFactory 類
...
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    // MapperProxy實(shí)現(xiàn)了InvocationHandler掂为,擴(kuò)展了invoke方法,維護(hù)代理邏輯员串。
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
...

這里我們可以看到動(dòng)態(tài)代理對接口的綁定勇哗,它的作用就是生成動(dòng)態(tài)代理對象。這里最終返回了CountryMapper接口的代理對象寸齐。

而代理對象則被放到了MapperProxy中欲诺。通過idea打斷點(diǎn),來查看CountryMapper的詳細(xì)信息渺鹦,我們也可以看到這是一個(gè)MapperProxy對象扰法。

image-20181028102601358

因此,在執(zhí)行countryMapper.selectAll()方法時(shí)毅厚,便會(huì)進(jìn)入到MapperProxy的invoke方法中來塞颁。

我們來看一下MapperProxy的部分代碼。

// MapperProxy類
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 判斷是否是一個(gè)類
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 顯然吸耿,我們這里是一個(gè)接口祠锣,則執(zhí)行下面的流程
    // 生成MapperMethod對象,通過cachedMapperMethod初始化
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 執(zhí)行execute方法咽安, 把sqlSession和當(dāng)前參數(shù)傳遞進(jìn)去
    return mapperMethod.execute(sqlSession, args);
}
...

接著來看看execute方法

// MapperMethod 類的方法
// MapperMethod采用命令模式運(yùn)行伴网,根據(jù)上下文跳轉(zhuǎn)

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        ...
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                // 主要看這個(gè)方法
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        ...
    }
}

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 將Java參數(shù)轉(zhuǎn)換為Sql命令行參數(shù)
    Object param = method.convertArgsToSqlCommandParam(args);
    // 是否需要分頁
    if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        // 通過SqlSession對象執(zhí)行查詢,帶分頁
        // command.getName() 獲取Mapper接口當(dāng)前執(zhí)行方法selectAll的全路徑名
        result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
        // 通過SqlSession對象執(zhí)行查詢妆棒,不帶分頁
        result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
        if (method.getReturnType().isArray()) {
            return convertToArray(result);
        } else {
            return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
    }
    return result;
}

至此已經(jīng)基本可以明白了澡腾,Mybatis為什么只用Mapper接口就可以運(yùn)行SQL,因?yàn)橛成淦鞯腦ML文件的命名空間對應(yīng)的便是這個(gè)接口的全路徑募逞,它能根據(jù)全路徑和方法名綁定起來蛋铆,通過動(dòng)態(tài)代理技術(shù)馋评,讓這個(gè)接口跑起來放接。然后采用命令模式,根據(jù)上下文跳轉(zhuǎn)留特,最終還是使用SqlSession接口的方法使得它能夠進(jìn)行查詢纠脾。

接著我們就來看看selectList的源碼。

result = sqlSession.<E>selectList(command.getName(), param);

注意這里的sqlSession蜕青,其實(shí)是DefaultSqlSession的對象苟蹈,因此要去看DefaultSqlSession的selectList方法。

@Override
public <E> List<E> selectList(String statement, Object parameter) {
    // 分頁參數(shù)選擇默認(rèn)右核,表示不分頁
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 從Configuration配置中獲取MappedStatement對象
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 使用executor進(jìn)行查詢
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

接著就進(jìn)入了Executor查詢之后慧脱,將結(jié)果返回。那么Executor是如何進(jìn)行查詢的呢贺喝?

下一篇文章再見菱鸥!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宗兼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子氮采,更是在濱河造成了極大的恐慌殷绍,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹊漠,死亡現(xiàn)場離奇詭異主到,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)躯概,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門登钥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人娶靡,你說我怎么就攤上這事怔鳖。” “怎么了固蛾?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵结执,是天一觀的道長。 經(jīng)常有香客問我艾凯,道長献幔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任趾诗,我火速辦了婚禮蜡感,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恃泪。我一直安慰自己郑兴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布贝乎。 她就那樣靜靜地躺著情连,像睡著了一般。 火紅的嫁衣襯著肌膚如雪览效。 梳的紋絲不亂的頭發(fā)上却舀,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音锤灿,去河邊找鬼挽拔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛但校,可吹牛的內(nèi)容都是我干的螃诅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼术裸!你這毒婦竟也來了空执?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤穗椅,失蹤者是張志新(化名)和其女友劉穎辨绊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匹表,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡门坷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袍镀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片默蚌。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖苇羡,靈堂內(nèi)的尸體忽然破棺而出绸吸,到底是詐尸還是另有隱情,我是刑警寧澤设江,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布锦茁,位于F島的核電站,受9級特大地震影響叉存,放射性物質(zhì)發(fā)生泄漏码俩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一歼捏、第九天 我趴在偏房一處隱蔽的房頂上張望稿存。 院中可真熱鬧,春花似錦瞳秽、人聲如沸瓣履。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袖迎。三九已至,卻和暖如春痰洒,著一層夾襖步出監(jiān)牢的瞬間瓢棒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工丘喻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人念颈。 一個(gè)月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓泉粉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子嗡靡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

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

  • 1 Mybatis入門 1.1 單獨(dú)使用jdbc編程問題總結(jié) 1.1.1 jdbc程序 上邊使...
    哇哈哈E閱讀 3,307評論 0 38
  • 單獨(dú)使用mybatis是有很多限制的(比如無法實(shí)現(xiàn)跨越多個(gè)session的事務(wù))跺撼,而且很多業(yè)務(wù)系統(tǒng)本來就是使用sp...
    七寸知架構(gòu)閱讀 3,450評論 0 53
  • # 前言 在java程序員的世界里,最熟悉的開源軟件除了 Spring讨彼,Tomcat歉井,還有誰呢?當(dāng)然是 Mybat...
    莫那一魯?shù)?/span>閱讀 3,298評論 3 11
  • 在床上待了一天哈误,肯定了自己一年以來狂補(bǔ)性知識的工作哩至,看了一些接地氣的帖子,把對未來的期待調(diào)整得寬泛而具體蜜自,艱難地作...
    胡涂格格閱讀 137評論 0 0
  • 今天5月21日星期二小雨 今天早上起床一看下雨了菩貌,起床做飯,寶寶們吃了飯重荠,寶爸去送的大寶箭阶,下雨了和二寶在家玩了一上...
    fba947bf0ca5閱讀 169評論 0 0