Mybatis到底是如何優(yōu)雅的實(shí)現(xiàn)的型雳。我和他之間還差很多個你

[TOC]

mybatis運(yùn)行分為兩部分奕锌,第一部分讀取配置文件緩存到Configuration對象中。用以創(chuàng)建SqlSessionFactory逞刷,第二部分是SqlSession的執(zhí)行過程嘉涌。

Mybatis基本認(rèn)識

動態(tài)代理

  • 之前我們知道Mapper僅僅是一個接口,而不是一個邏輯實(shí)現(xiàn)類夸浅。但是在Java中接口是無法執(zhí)行邏輯的仑最。這里Mybatis就是通過動態(tài)代理實(shí)現(xiàn)的。關(guān)于動態(tài)代理我們常用的有Jdk動態(tài)代理和cglib動態(tài)代理帆喇。兩種卻別這里不做贅述警医。關(guān)于CGLIB代理在框架中使用的比較多。

  • 關(guān)于動態(tài)代理就是所有的請求有一個入口番枚,由這個入口進(jìn)行分發(fā)法严。在開發(fā)領(lǐng)域的一個用途就是【負(fù)載均衡】

  • 關(guān)于Mybatis的動態(tài)代理是使用了兩種的結(jié)合。

  • 下面看看JDK和cglib兩種實(shí)現(xiàn)

JDK實(shí)現(xiàn)

  • 首先我們需要提供一個接口 , 這個接口是對我們程序員的一個抽象葫笼。 擁有編碼和改BUG的本領(lǐng)

public interface Developer {

    /**
     * 編碼
     */
    void code();

    /**
     * 解決問題
     */
    void debug();
}

  • 關(guān)于這兩種本領(lǐng)每個人處理方式不同深啤。這里我們需要一個具體的實(shí)例對象

public class JavaDeveloper implements Developer {
    @Override
    public void code() {
        System.out.println("java code");
    }

    @Override
    public void debug() {
        System.out.println("java debug");
    }
}

  • 我們傳統(tǒng)的調(diào)用方式是通過java提供的new 機(jī)制創(chuàng)造一個JavaDeveloper對象出來。而通過動態(tài)代理是通過java.lang.reflect.Proxy對象創(chuàng)建對象調(diào)用實(shí)際方法的路星。

  • 通過newProxyInstance方法獲取接口對象的溯街。而這個方法需要三個參數(shù)
    ClassLoader loader : 通過實(shí)際接口實(shí)例對象獲取ClassLoader
    Class<?>[] interfaces : 我們抽象的接口
    InvocationHandler h : 對我們接口對象方法的調(diào)用。在調(diào)用節(jié)點(diǎn)我們可以進(jìn)行我們的業(yè)務(wù)攔截


JavaDeveloper jDeveloper = new JavaDeveloper();
Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> {
    if (method.getName().equals("code")) {
        System.out.println("我是一個特殊的人洋丐,code之前先分析問題");
        return method.invoke(jDeveloper, params);
    }
    if (method.getName().equals("debug")) {
        System.out.println("我沒有bug");

    }
    return null;
});
developer.code();
developer.debug();

CGLIB動態(tài)代理

  • cglib動態(tài)代理優(yōu)點(diǎn)在于他不需要我們提前準(zhǔn)備接口呈昔。他代理的實(shí)際的對象。這對于我們開發(fā)來說就很方便了友绝。

public class HelloService {
    public HelloService() {
        System.out.println("HelloService構(gòu)造");
    }

    final public String sayHello(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }

    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}

  • 下面我們只需要實(shí)現(xiàn)cglib提供的MethodInterceptor接口堤尾,在初始化設(shè)置cglib的時候加載這個實(shí)例化對象就可以了

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}

  • 下面我們就來初始化設(shè)置cglib

public static void main(String[] args) {
    //代理類class文件存入本地磁盤方便我們反編譯查看源代碼
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
    //通過CGLIB動態(tài)代理獲取代理對象過程
    Enhancer enhancer = new Enhancer();
    //設(shè)置enhancer對象的父類
    enhancer.setSuperclass(HelloService.class);
    // 設(shè)置enhancer的回調(diào)對象
    enhancer.setCallback(new MyMethodInterceptor());
    //創(chuàng)建代理對象
    HelloService helloService = (HelloService) enhancer.create();
    //通過代理對象調(diào)用目標(biāo)方法
    helloService.sayHello();
}

  • 仔細(xì)看看cglib和spring的aop特別像。針對切點(diǎn)進(jìn)行切面攔截控制迁客。

總結(jié)

  • 通過對比兩種動態(tài)代理我們很容易發(fā)現(xiàn)郭宝,mybatis就是通過JDK代理實(shí)現(xiàn)Mapper調(diào)用的。我們Mapper接口實(shí)現(xiàn)通過代理到xml中對應(yīng)的sql執(zhí)行邏輯

反射

  • 相信有一定經(jīng)驗(yàn)的Java工程師都對反射或多或少有一定了解掷漱。其實(shí)從思想上看不慣哪種語言都是有反射的機(jī)制的粘室。
  • 通過反射我們就擺脫了對象的限制我們調(diào)用方法不再需要通過對象調(diào)用了〔贩叮可以通過Class對象獲取方法對象衔统。從而通過invoke方法進(jìn)行方法的調(diào)用了。

Configuration對象作用

  • Configuration對象存儲了所有Mybatis的配置。主要初始化一下參數(shù)
    • properties
    • settings
    • typeAliases
    • typeHandler
    • ObjectFactory
    • plugins
    • environment
    • DatabaseIdProvider
    • Mapper映射器

映射器結(jié)構(gòu)

image
  • BoundSql提供三個主要的屬性 parameterMappings 锦爵、parameterObject舱殿、sql

  • parameterObject參數(shù)本身。我們可以傳遞java基本類型棉浸、POJO怀薛、Map或者@Param標(biāo)注的參數(shù)。

  • 當(dāng)我們傳遞的是java基本類型mybatis會轉(zhuǎn)換成對應(yīng)的包裝對象 int -> Integer

  • 如果我們傳遞POJO迷郑、Map枝恋。就是對象本身

  • 我們傳遞多個參數(shù)且沒有@Param指定變量名則parameterObject 類似
    {"1":p1,"2":p2,"param1":p1,"param2":p2}

  • 我們傳遞多個參數(shù)且@Param指定變量名 則parameterObject類似
    {"key1":p1,"key2":p2,"param1":p1,"param2":p2}

  • parameterMapping 是記錄屬性、名稱嗡害、表達(dá)式焚碌、javaType,jdbcType、typeHandler這些信息

  • sql 屬性就是我們映射器中的一條sql. 正常我們在常見中對sql進(jìn)行校驗(yàn)霸妹。正常不需要修改sql十电。

sqlsession執(zhí)行流程(源碼跟蹤)

  • 首先我們看看我們平時開發(fā)的Mapper接口是如何動態(tài)代理的。這就需要提到MapperProxyFactory這個類了叹螟。該類中的newInstance方法

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  • 通過上滿代碼及上述對jdk動態(tài)代理的表述鹃骂。我們可以知道m(xù)apperProxy是我們代理的重點(diǎn)。
  • MapperProxy是InvocationHandler的實(shí)現(xiàn)類罢绽。他重寫的invoke方法就是代理對象執(zhí)行的方法入口畏线。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
    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);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}


private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
    & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
    && method.getDeclaringClass().isInterface();
}

  • 通過源碼發(fā)現(xiàn)。invoke內(nèi)部首先判斷對象是否是類 良价。 通過打斷點(diǎn)發(fā)現(xiàn)最終會走到cacheMapperMethod這個方法去創(chuàng)建MapperMethod對象寝殴。
  • 繼續(xù)查看MapperMethod中execute方法我們可以了解到內(nèi)部實(shí)現(xiàn)其實(shí)是一個命令行模式開發(fā)。通過判斷命令從而執(zhí)行不同的語句明垢。判斷到具體執(zhí)行語句然后將參數(shù)傳遞給sqlsession進(jìn)行sql調(diào)用并獲取結(jié)果蚣常。到了sqlsession就和正常jdbc開發(fā)sql進(jìn)行關(guān)聯(lián)了。sqlsession中Executor痊银、StatementHandler抵蚊、ParameterHandlerResulthandler四大天王

Executor

  • 顧名思義他就是一個執(zhí)行器溯革。將java提供的sql提交到數(shù)據(jù)庫泌射。Mybatis提供了三種執(zhí)行器。

  • Configuration.classnewExecutor源碼

image
  • 根據(jù)uml我們不難看出mybatis中提供了三類執(zhí)行器分別SimpleExecutor鬓照、ReuseExecutor、BatchExecutor

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

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 得到configuration 中的environment
      final Environment environment = configuration.getEnvironment();
      // 得到configuration 中的事務(wù)工廠
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 獲取執(zhí)行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 返回默認(rèn)的SqlSession
      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();
    }
  }

  • 通過上述源碼我們知道在sqlsession獲取一個數(shù)據(jù)庫session對象時我們或根據(jù)我們的settings配置加載一個Executor對象孤紧。在settings中配置也很簡單

<settings>
<!--取值范圍 SIMPLE, REUSE, BATCH -->
    <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

  • 我們也可以通過java代碼設(shè)置

factory.openSession(ExecutorType.BATCH);

StatementHandler

  • 顧名思義豺裆,StatementHandler就是專門處理數(shù)據(jù)庫回話的。這個對象的創(chuàng)建還是在Configuration中管理的。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  • 很明顯Mybatis中StatementHandler使用的是RoutingStatementHandler這個class
image
  • 關(guān)于StatementHandler和RoutingStatementHandler之間的關(guān)系我們通過源碼可以看出這里和Executor一樣都是適配器模式臭猜。采用這種模式的好處是方便我們對這些對象進(jìn)行代理躺酒。這里讀者可以猜測一下是使用了哪種動態(tài)代理。給點(diǎn)提示 這里使用了接口哦
image

image
  • 在查看BaseStatementHandler結(jié)構(gòu)我們會發(fā)現(xiàn)和Executor一模一樣蔑歌。同樣的Mybatis在構(gòu)造RoutingStatementHandler的時候會根據(jù)setting中配置來加載不同的具體子類羹应。這些子類都是繼承了BaseStatementHandler.

  • 前一節(jié)我們跟蹤了Executor。 我們知道Mybatis默認(rèn)的是SimpleExecutor次屠。 StatementHandler我們跟蹤了Mybaits默認(rèn)的是PrePareStatementHandler园匹。在SimpleExecutor執(zhí)行查詢的源碼如下

image

image
  • 我們發(fā)現(xiàn)在executor查詢錢會先讓statementHandler構(gòu)建一個Statement對象。最終就是StatementHandler中prepare方法劫灶。這個方法在抽象類BaseStatmentHandler中已經(jīng)封裝好了裸违。
image
  • 這個方法的邏輯是初始化statement和設(shè)置連接超時等一些輔助作用
  • 然后就是設(shè)置一些參數(shù)等設(shè)置。最后就走到了執(zhí)行器executor的doquery
image
  • PrepareStatement在我們jdbc開發(fā)時是常見的一個類 本昏。 這個方法執(zhí)行execute前我們需要設(shè)置sql語句供汛,設(shè)置參數(shù)進(jìn)行編譯。這一系列步驟就是剛才我們說的流程也是PrepareStatementHandler.prepareStatement幫我們做的事情涌穆。那么剩下的我們也很容易想到就是我們對數(shù)據(jù)結(jié)果的封裝怔昨。正如代碼所示下馬就是resultSetHandler幫我們做事情了。

結(jié)果處理器(ResultSetHandler)


@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

  • 這個方法我們可以導(dǎo)出來是結(jié)果xml中標(biāo)簽配置對結(jié)果的一個封裝宿稀。

總結(jié)

  • SqlSession在一個查詢開啟的時候會先通過CacheExecutor查詢緩存趁舀。擊穿緩存后會通過BaseExector子類的SimpleExecutor創(chuàng)建StatementHandler。PrepareStatementHandler會基于PrepareStament執(zhí)行數(shù)據(jù)庫操作原叮。并針對返回結(jié)果通過ResultSetHandler返回結(jié)果數(shù)據(jù)
image

主題

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赫编,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奋隶,更是在濱河造成了極大的恐慌擂送,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唯欣,死亡現(xiàn)場離奇詭異嘹吨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)境氢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蟀拷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萍聊,你說我怎么就攤上這事问芬。” “怎么了寿桨?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵此衅,是天一觀的道長强戴。 經(jīng)常有香客問我,道長挡鞍,這世上最難降的妖魔是什么骑歹? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮墨微,結(jié)果婚禮上道媚,老公的妹妹穿的比我還像新娘。我一直安慰自己翘县,他們只是感情好最域,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炼蹦,像睡著了一般羡宙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掐隐,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天狗热,我揣著相機(jī)與錄音,去河邊找鬼虑省。 笑死匿刮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的探颈。 我是一名探鬼主播熟丸,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伪节!你這毒婦竟也來了光羞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤怀大,失蹤者是張志新(化名)和其女友劉穎纱兑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體化借,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潜慎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓖康。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铐炫。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蒜焊,靈堂內(nèi)的尸體忽然破棺而出倒信,到底是詐尸還是另有隱情,我是刑警寧澤泳梆,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布鳖悠,位于F島的核電站唆迁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏竞穷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一鳞溉、第九天 我趴在偏房一處隱蔽的房頂上張望瘾带。 院中可真熱鬧,春花似錦熟菲、人聲如沸看政。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允蚣。三九已至,卻和暖如春呆贿,著一層夾襖步出監(jiān)牢的瞬間嚷兔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工做入, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冒晰,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓竟块,卻偏偏與公主長得像壶运,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浪秘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345