<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          [干貨]源碼級探究Mybatis原理-以查詢?yōu)槔?/h1>

          共 21338字,需瀏覽 43分鐘

           ·

          2022-02-25 16:57


          作為一名Java后端開發(fā)者,尤其是國內(nèi)開發(fā)者,從剛參加工作開始就與Mybatis打交道了。

          用了這么久的Mybatis難免會心生疑問:

          • 我只是寫了個Mapper接口,再配合xml或者注解,把SQL一寫,就可以執(zhí)行數(shù)據(jù)庫操作,這是為何?
          • 都說Mybatis是對JDBC的封裝,可是我卻看不到JDBC相關(guān)的接口和對象,它們到哪里去了?
          • 為什么在Spring中使用Mybatis,不用加@Repository/@Component之類的注解,就可以隨用隨注入(如:@Autowired)?
          ?

          硬核萬字長文,點個看,轉(zhuǎn)發(fā),多謝啦~

          ?

          隨著工作經(jīng)驗越多,對這些問題的疑惑就會越發(fā)強烈。而讀源碼是解決這些疑問的根本方法。

          那么就跟隨筆者的腳步,試著用一篇文章,以一個查詢?yōu)槔?,從源碼角度一步一步揭開Mybatis的神秘面紗。

          一、先看一個demo

          ????private?SqlSessionFactory?sqlSessionFactory;

          ????@Before
          ????public?void?prepare()?throws?IOException?{
          ????????String?resource?=?"mybatis-config.xml";
          ????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);
          ????????sqlSessionFactory?=?new?SqlSessionFactoryBuilder().build(inputStream);
          ????}

          ????/**
          ?????*?通過?SqlSession.getMapper(XXXMapper.class)??接口方式
          ?????*?@throws?IOException
          ?????*/
          ????@Test
          ????public?void?testSelect()?throws?IOException?{
          ????????SqlSession?session?=?sqlSessionFactory.openSession();?//?ExecutorType.BATCH
          ????????try?{
          ????????????BlogMapper?mapper?=?session.getMapper(BlogMapper.class);
          ????????????Blog?blog?=?mapper.selectBlogById(1);
          ????????????System.out.println(blog);
          ????????}?finally?{
          ????????????session.close();
          ????????}
          ????}

          這是一個非Spring項目的Test用例類,邏輯很直觀,就是在測試通過id查詢一行記錄;在執(zhí)行查詢之間加載配置文件。

          執(zhí)行該測試用例,日志輸出如下:

          ????Opening?JDBC?Connection
          ????Created?connection?1325808650.
          ????Setting?autocommit?to?false?on?JDBC?Connection?[com.mysql.jdbc.JDBC4Connection@4f063c0a]
          ????==>??Preparing:?select?*?from?blog?where?bid?=???
          ????==>?Parameters:?1(Integer)
          ????<==????Columns:?bid,?name,?author_id,?type
          ????<==????????Row:?1,?RabbitMQ延時消息,?1001,?0
          ????getNullableResult---1NORMAL
          ????<==??????Total:?1
          ????Blog(bid=1,?name=RabbitMQ延時消息,?authorId=1001,?blogType=0)

          我們就通過這個ById查詢的案例,對Mybatis運行的過程抽絲剝繭,還原出一個完整的脈絡(luò)。

          二、一圖總覽全局

          ?

          按照慣例我們用一張簡單概括的流程圖引領(lǐng)全局,先建立一個宏觀的印象。

          ?

          基本脈絡(luò).png

          從圖中可以看到,Mybatis主要的工作流程分為以下幾步:

          1. 加載并解析配置文件
          2. 獲取SqlSession對象作為與數(shù)據(jù)庫交互的接口
          3. 通過Executor對象封裝數(shù)據(jù)庫操作,執(zhí)行SQL操作
          4. 調(diào)用底層的JDBC接口,與數(shù)據(jù)庫進行真正的交互
          5. 向數(shù)據(jù)庫提交參數(shù),并封裝返回參數(shù)

          加載并解析配置文件

          在Mybatis啟動的時候會去加載配置文件,一般來說文件包含全局配置文件(文件名為 「mybatis-config.xml」) ,以及映射器配置文件(也就是各種Mapper.xml文件);

          獲取SqlSession對象作為與數(shù)據(jù)庫交互的接口

          Mybatis在加載完配置文件之后,會去獲取SqlSession對象,這個對象是應(yīng)用程序與數(shù)據(jù)庫之間的橋梁,封裝了程序與數(shù)據(jù)庫之間的連接。

          一般來說,一個SqlSession對象中包含了一個Connection,我們都知道Connection是線程不安全的,因此導致SqlSession對象也是線程不安全的。因此如果將SqlSession作為成員變量使用,存在風險。(應(yīng)當使用SqlSessionTemplate,這部分后面再說)。

          ?

          注意:SqlSession是提供給應(yīng)用層的一個訪問數(shù)據(jù)庫的接口,它并不是真正的SQL執(zhí)行者,它內(nèi)部封裝了JDBC核心對象,如Statement,ResultSet等。

          ?

          通過SqlSessionFactory獲取SqlSession會話

          如果要獲取一個SqlSession會話,就需要有會話工廠,即:SqlSessionFactory。它包含了所有的配置信息,而Factory又是通過Builder創(chuàng)建的,這部分后文代碼分析中會說。

          通過Executor對象封裝數(shù)據(jù)庫操作,執(zhí)行SQL操作

          SqlSession持有Executor對象,Executor在執(zhí)行query、update、insert等操作時,會創(chuàng)建一系列的對象處理參數(shù)、處理結(jié)果集,核心的對象是StatementHandler,它本質(zhì)上是對Statement的封裝。

          三、走進源碼,一探究竟

          3.1 SqlSessionFactory的創(chuàng)建

          ?

          首先是SqlSession的創(chuàng)建過程;SqlSession需要通過SqlSessionFactory創(chuàng)建,而SqlSessionFactory又是通過SqlSessionFactoryBuilder創(chuàng)建的。

          ?
          ????#?org.apache.ibatis.session.SqlSessionFactoryBuilder#build
          ????public?SqlSessionFactory?build(InputStream?inputStream)?{
          ??????return?build(inputStream,?null,?null);
          ????}

          事實上,inputStream就是配置文件的文件輸入流,它傳給了SqlSessionFactoryBuilder的build重載方法,我們看一下這個方法的實現(xiàn)。

          ????public?SqlSessionFactory?build(InputStream?inputStream,?String?environment,?Properties?properties)?{
          ??????try?{
          ????????//?用于解析?mybatis-config.xml,同時創(chuàng)建了?Configuration?對象?>>
          ????????XMLConfigBuilder?parser?=?new?XMLConfigBuilder(inputStream,?environment,?properties);
          ????????//?解析XML,最終返回一個?DefaultSqlSessionFactory?>>
          ????????return?build(parser.parse());
          ??????}?catch?(Exception?e)?{
          ????????throw?ExceptionFactory.wrapException("Error?building?SqlSession.",?e);
          ??????}?finally?{
          ????????ErrorContext.instance().reset();
          ????????try?{
          ??????????inputStream.close();
          ????????}?catch?(IOException?e)?{
          ??????????//?Intentionally?ignore.?Prefer?previous?error.
          ????????}
          ??????}
          ????}

          可以看到,SqlSessionFactoryBuilder底層是通過xml解析方式,對配置文件進行解析,并基于解析的結(jié)果構(gòu)建了SqlSessionFactory的實例,這里返回的是默認的SqlSessionFactory--->DefaultSqlSessionFactory。

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

          「注意:此處就已經(jīng)通過配置文件解析出了Configuration,并通過DefaultSqlSessionFactory構(gòu)造方法創(chuàng)建了DefaultSqlSessionFactory實例。后文要用!」

          ?

          xml解析過程,感興趣的讀者可以自行研究,簡單的說無非就是對xml文件的dom節(jié)點進行讀取和匹配,獲取屬性加載到內(nèi)存,Mybatis自己基于javax的xml操作api封裝了一個工具類,「org.apache.ibatis.parsing.XPathParser」 。

          ?

          3.2 SqlSession的創(chuàng)建

          在使用的demo中,我們通過SqlSessionFactory獲取到一個SqlSession實例。

          ????SqlSession?session?=?sqlSessionFactory.openSession();

          進入 openSession 方法一探究竟。

          ????#?org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
          ????public?SqlSession?openSession()?{
          ??????return?openSessionFromDataSource(configuration.getDefaultExecutorType(),?null,?false);
          ????}

          繼續(xù)進入 openSessionFromDataSource 方法:

          ????private?SqlSession?openSessionFromDataSource(ExecutorType?execType,?TransactionIsolationLevel?level,?boolean?autoCommit)?{
          ??????Transaction?tx?=?null;
          ??????try?{
          ????????final?Environment?environment?=?configuration.getEnvironment();
          ????????//?獲取事務(wù)工廠
          ????????final?TransactionFactory?transactionFactory?=?getTransactionFactoryFromEnvironment(environment);
          ????????//?創(chuàng)建事務(wù)
          ????????tx?=?transactionFactory.newTransaction(environment.getDataSource(),?level,?autoCommit);
          ????????//?根據(jù)事務(wù)工廠和默認的執(zhí)行器類型,創(chuàng)建執(zhí)行器?>>
          ????????final?Executor?executor?=?configuration.newExecutor(tx,?execType);
          ????????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();
          ??????}
          ????}

          這里的邏輯比較核心,主要做了幾件事:

          1. 獲取到事務(wù)工廠;
          2. 通過事務(wù)工廠創(chuàng)建了事務(wù),如果是使用Spring框架,則由Spring框架開啟事務(wù);
          3. 根據(jù)事務(wù)工廠和默認的執(zhí)行器類型,創(chuàng)建執(zhí)行器

          最后通過DefaultSqlSession的構(gòu)造方法,創(chuàng)建出DefaultSqlSession實例,它是SqlSession接口的默認實現(xiàn)。

          到此,我們就持有了一個SqlSession對象,并且它還持有了一個Executor執(zhí)行器實例。

          代理Mapper對象,執(zhí)行SQL

          回到我們的demo代碼中:

          ????@Test
          ????public?void?testSelect()?throws?IOException?{
          ????????SqlSession?session?=?sqlSessionFactory.openSession();?//?ExecutorType.BATCH
          ????????try?{
          ????????????//?重點看這行代碼
          ????????????BlogMapper?mapper?=?session.getMapper(BlogMapper.class);
          ????????????Blog?blog?=?mapper.selectBlogById(1);
          ????????????System.out.println(blog);
          ????????}?finally?{
          ????????????session.close();
          ????????}
          ????}

          我們已經(jīng)拿到了SqlSession,接著通過 「session.getMapper(BlogMapper.class)」; 獲取到了BlogMapper接口的實現(xiàn)類。

          注意,我說的并不是獲取到了BlogMapper,因為大家使用過Mybatis框架都知道BlogMapper是個接口,那么此處拿到的,必然是BlogMapper的實例。

          接口的實例,嗯,有點意思了,我們明明只寫了個接口,并沒有實現(xiàn)這個接口???

          是不是想到了什么?對,就是動態(tài)代理。

          此處獲取到的Mapper實例,就是Mybatis框架幫我們創(chuàng)建出的代理對象。

          ?

          進入 DefaultSqlSession#getMapper 方法

          ?
          ????@Override
          ????public??T?getMapper(Class?type)?{
          ??????return?configuration.getMapper(type,?this);
          ????}

          ok,繼續(xù)往下看:

          ????public??T?getMapper(Class?type,?SqlSession?sqlSession)?{
          ??????return?mapperRegistry.getMapper(type,?sqlSession);
          ????}

          這里,我們發(fā)現(xiàn)Mapper對象是通過 mapperRegistry 這個所謂的Mapper注冊中心中獲取到的,它的數(shù)據(jù)結(jié)構(gòu)是一個HashMap:

          ????#?org.apache.ibatis.session.Configuration
          ????protected?final?MapperRegistry?mapperRegistry?=?new?MapperRegistry(this);

          ????#?org.apache.ibatis.binding.MapperRegistry
          ????public?class?MapperRegistry?{
          ??????private?final?Configuration?config;
          ??????private?final?Map,?MapperProxyFactory>?knownMappers?=?new?HashMap<>();

          既然我們能夠通過Mapper接口類型get到接口的代理類,那它是多會兒put到Map里的?

          仔細想一下應(yīng)當能夠想到,我們此時已經(jīng)是在sql的執(zhí)行期了,在這之前必然是配置文件的解析期間執(zhí)行的put操作。具體代碼如下:

          ????/**
          ????*?org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
          ????*?Mapper解析
          ????*?@param?parent
          ????*?@throws?Exception
          ????*/
          ????private?void?mapperElement(XNode?parent)?throws?Exception?{
          ??????if?(parent?!=?null)?{
          ????????for?(XNode?child?:?parent.getChildren())?{
          ??????????//?不同的定義方式的掃描,最終都是調(diào)用?addMapper()方法
          ??????????//?(添加到 MapperRegistry)。這個方法和 getMapper()?對應(yīng)

          ??????????//?package?包

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

          ??????????????//?resource?相對路徑

          ??????????????ErrorContext.instance().resource(resource);
          ??????????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);
          ??????????????XMLMapperBuilder?mapperParser?=?new?XMLMapperBuilder(inputStream,?configuration,?resource,?configuration.getSqlFragments());
          ??????????????//?解析?Mapper.xml,總體上做了兩件事情?>>
          ??????????????mapperParser.parse();

          ????????????}?else?if?(resource?==?null?&&?url?!=?null?&&?mapperClass?==?null)?{

          ??????????????//?url?絕對路徑

          ??????????????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??單個接口
          ??????????????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.");
          ????????????}
          ??????????}
          ????????}
          ??????}
          ????}

          通過這段代碼我們可以看到,無論是通過指定掃描包路徑,還是resources相對路徑,或者url絕對路徑,或者單個Mapper添加的方式,Mybatis本質(zhì)上都是通過 「addMapper()方法添加到 MapperRegistry」。

          ?

          繼續(xù)回到Mapper代理對象創(chuàng)建過程中來。

          ?
          ????#?org.apache.ibatis.session.Configuration#getMapper
          ????public??T?getMapper(Class?type,?SqlSession?sqlSession)?{
          ??????return?mapperRegistry.getMapper(type,?sqlSession);
          ????}

          繼續(xù)看mapperRegistry.getMapper方法邏輯。

          ????public??T?getMapper(Class?type,?SqlSession?sqlSession)?{
          ??????final?MapperProxyFactory?mapperProxyFactory?=?(MapperProxyFactory)?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);
          ??????}
          ????}

          我們發(fā)現(xiàn),通過接口類型從HashMap中取到了一個 「MapperProxyFactory」 Mapper代理工廠的實例。

          ?

          MapperProxyFactory實際上是對Mapper接口的包裝,我們只需要看源碼就知道了。

          ?
          ????public?class?MapperProxyFactory?{

          ??????private?final?Class?mapperInterface;
          ??????private?final?Map?methodCache?=?new?ConcurrentHashMap<>();
          ?

          構(gòu)造方法接受一個Mapper的class類型,對其進行封裝。

          ?
          ??????public?MapperProxyFactory(Class?mapperInterface)?{
          ????????this.mapperInterface?=?mapperInterface;
          ??????}

          獲取到MapperProxyFactory實例之后,通過 「mapperProxyFactory.newInstance(sqlSession)」 就創(chuàng)建出了Mapper的代理對象。

          ??????public?T?newInstance(SqlSession?sqlSession)?{
          ????????final?MapperProxy?mapperProxy?=?new?MapperProxy<>(sqlSession,?mapperInterface,?methodCache);
          ????????return?newInstance(mapperProxy);
          ??????}

          這里通過SqlSession、Mapper接口、方法緩存(「簡單的說就是Mapper的那一堆方法,每次反射創(chuàng)建太耗費性能了,就緩存到一個Map里」)創(chuàng)建出MapperProxy 對象,進一步調(diào)用的如下方法:

          ??????@SuppressWarnings("unchecked")
          ??????protected?T?newInstance(MapperProxy?mapperProxy)?{
          ????????// 1:類加載器:2:被代理類實現(xiàn)的接口、3:實現(xiàn)了 InvocationHandler 的觸發(fā)管理類
          ????????return?(T)?Proxy.newProxyInstance(mapperInterface.getClassLoader(),?new?Class[]?{?mapperInterface?},?mapperProxy);
          ??????}

          這里把創(chuàng)建代理對象的操作委托給了MapperProxy,「我們發(fā)現(xiàn),它的核心就是創(chuàng)建代理Mapper的代理對象 (h對象)?!?/strong>

          MapperProxy具體是如何創(chuàng)建的Mapper代理?

          我們都知道,動態(tài)代理在JDK中是通過實現(xiàn)InvocationHandler接口實現(xiàn)的,那么大膽猜想MapperProxy必然實現(xiàn)了InvocationHandler接口。

          ????public?class?MapperProxy?implements?InvocationHandler,?Serializable?{

          果然如此。

          我們來看它的invoke方法實現(xiàn):

          ????@Override
          ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
          ??????try?{
          ????????//?toString?hashCode?equals?getClass等方法,無需走到執(zhí)行SQL的流程
          ????????if?(Object.class.equals(method.getDeclaringClass()))?{
          ??????????return?method.invoke(this,?args);
          ????????}?else?{
          ??????????//?提升獲取?mapperMethod?的效率,到?MapperMethodInvoker(內(nèi)部接口)?的?invoke
          ??????????//?普通方法會走到?PlainMethodInvoker(內(nèi)部類)?的?invoke
          ??????????return?cachedInvoker(method).invoke(proxy,?method,?args,?sqlSession);
          ????????}
          ??????}?catch?(Throwable?t)?{
          ????????throw?ExceptionUtil.unwrapThrowable(t);
          ??????}
          ????}

          可以看到,如果是普通方法,直接執(zhí)行,不需要特殊處理;

          否則就獲取匹配的緩存Mapper方法,執(zhí)行數(shù)據(jù)庫操作。

          ?

          重點看一下 ?cachedInvoker(method).invoke(proxy, method, args, sqlSession); 邏輯。

          ?
          ????private?MapperMethodInvoker?cachedInvoker(Method?method)?throws?Throwable?{
          ??????try?{

          ????????//?Java8?中?Map?的方法,根據(jù)?key?獲取值,如果值是?null,則把后面Object?的值賦給?key
          ????????//?如果獲取不到,就創(chuàng)建
          ????????//?獲取的是?MapperMethodInvoker(接口)?對象,只有一個invoke方法

          ????????return?methodCache.computeIfAbsent(method,?m?->?{
          ??????????if?(m.isDefault())?{

          ????????????//?接口的默認方法(Java8),只要實現(xiàn)接口都會繼承接口的默認方法,例如?List.sort()

          ????????????try?{
          ??????????????if?(privateLookupInMethod?==?null)?{
          ????????????????return?new?DefaultMethodInvoker(getMethodHandleJava8(method));
          ??????????????}?else?{
          ????????????????return?new?DefaultMethodInvoker(getMethodHandleJava9(method));
          ??????????????}
          ????????????}?catch?(IllegalAccessException?|?InstantiationException?|?InvocationTargetException
          ????????????????|?NoSuchMethodException?e)?{
          ??????????????throw?new?RuntimeException(e);
          ????????????}

          ??????????}?else?{

          ????????????//?創(chuàng)建了一個?MapperMethod
          ????????????return?new?PlainMethodInvoker(new?MapperMethod(mapperInterface,?method,?sqlSession.getConfiguration()));

          ??????????}
          ????????});
          ??????}?catch?(RuntimeException?re)?{
          ????????Throwable?cause?=?re.getCause();
          ????????throw?cause?==?null???re?:?cause;
          ??????}
          ????}

          這里針對Java8接口的默認方法做了些處理,這個地方不用特殊關(guān)注,我們重點看else邏輯:

          ??????//?創(chuàng)建了一個?MapperMethod
          ??????return?new?PlainMethodInvoker(
          ??????????new?MapperMethod(
          ????????????mapperInterface,?
          ????????????method,
          ????????????sqlSession.getConfiguration()));

          Mybatis執(zhí)行sql語句的真正開端:

          ?

          上文中,我們費盡努力,獲取到了 「PlainMethodInvoker」 實例,其實到這里,才是Mybatis執(zhí)行SQL真正的起點。

          ?

          不要慌,繼續(xù)跟上我的腳步,我們一鼓作氣往后看。

          上文中,我們知道Mapper對象實際上是Mapper接口的代理對象,而且是JDK的動態(tài)代理。

          當執(zhí)行Mapper的各種數(shù)據(jù)庫操作方法時,實際上是調(diào)用的代理對象的方法,也就是invoke方法。

          對于Mapper方法而言,其實就是調(diào)用的PlainMethodInvoker的invoke方法。

          忘了?那么我們再復(fù)習一下這部分的代碼:

          ??????//?org.apache.ibatis.binding.MapperProxy#invoke
          ??????//?普通方法會走到?PlainMethodInvoker(內(nèi)部類)?的?invoke
          ??????return?cachedInvoker(method).invoke(proxy,?method,?args,?sqlSession);

          接著來看PlainMethodInvoker的invoke方法:

          ????@Override
          ????public?Object?invoke(
          ????????????????????????Object?proxy,?
          ????????????????????????Method?method,?
          ????????????????????????Object[]?args,?
          ????????????????????????SqlSession?sqlSession)?throws?Throwable?{
          ??????//?SQL執(zhí)行的真正起點
          ??????return?mapperMethod.execute(sqlSession,?args);
          ????}

          實際上這里的mapperMethod就是我們Mapper接口或者說XML文件中定義的方法名了。

          ?

          接著就是重頭戲,MapperMethod#execute 方法,完整代碼我貼這兒了。

          ?
          ????//?org.apache.ibatis.binding.MapperMethod#execute
          ????public?Object?execute(SqlSession?sqlSession,?Object[]?args)?{
          ??????Object?result;
          ??????switch?(command.getType())?{
          ????????case?INSERT:?{
          ??????????Object?param?=?method.convertArgsToSqlCommandParam(args);
          ??????????result?=?rowCountResult(sqlSession.insert(command.getName(),?param));
          ??????????break;
          ????????}
          ????????case?UPDATE:?{
          ??????????Object?param?=?method.convertArgsToSqlCommandParam(args);
          ??????????result?=?rowCountResult(sqlSession.update(command.getName(),?param));
          ??????????break;
          ????????}
          ????????case?DELETE:?{
          ??????????Object?param?=?method.convertArgsToSqlCommandParam(args);
          ??????????result?=?rowCountResult(sqlSession.delete(command.getName(),?param));
          ??????????break;
          ????????}
          ????????case?SELECT:
          ??????????if?(method.returnsVoid()?&&?method.hasResultHandler())?{
          ????????????executeWithResultHandler(sqlSession,?args);
          ????????????result?=?null;
          ??????????}?else?if?(method.returnsMany())?{
          ????????????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);
          ????????????//?普通?select?語句的執(zhí)行入口?>>
          ????????????result?=?sqlSession.selectOne(command.getName(),?param);
          ????????????if?(method.returnsOptional()
          ????????????????&&?(result?==?null?||?!method.getReturnType().equals(result.getClass())))?{
          ??????????????result?=?Optional.ofNullable(result);
          ????????????}
          ??????????}
          ??????????break;
          ????????case?FLUSH:
          ??????????result?=?sqlSession.flushStatements();
          ??????????break;
          ????????default:
          ??????????throw?new?BindingException("Unknown?execution?method?for:?"?+?command.getName());
          ??????}
          ??????if?(result?==?null?&&?method.getReturnType().isPrimitive()?&&?!method.returnsVoid())?{
          ????????throw?new?BindingException("Mapper?method?'"?+?command.getName()
          ????????????+?"?attempted?to?return?null?from?a?method?with?a?primitive?return?type?("?+?method.getReturnType()?+?").");
          ??????}
          ??????return?result;
          ????}

          重點看那個switch case,不用注釋一眼看過去基本上也能看個八九不離十,這里就是通過sql的類型去執(zhí)行不同的jdbc操作。

          ?

          可以看到,熟悉的操作他來了,通過SqlSession完成一系列的數(shù)據(jù)庫操作。

          ?

          我們的demo是一個查詢操作,那么我們就挑select來看看。

          普通select語句的入口如下:

          ????result?=?sqlSession.selectOne(command.getName(),?param);

          繼續(xù)深入:

          ????//?DefaultSqlSession#selectOne(java.lang.String,?java.lang.Object)
          ????@Override
          ????public??T?selectOne(String?statement,?Object?parameter)?{
          ??????//?來到了?DefaultSqlSession
          ??????//?Popular?vote?was?to?return?null?on?0?results?and?throw?exception?on?too?many.
          ??????List?list?=?this.selectList(statement,?parameter);
          ??????if?(list.size()?==?1)?{
          ????????return?list.get(0);
          ??????}?else?if?(list.size()?>?1)?{
          ????????throw?new?TooManyResultsException("Expected?one?result?(or?null)?to?be?returned?by?selectOne(),?but?found:?"?+?list.size());
          ??????}?else?{
          ????????return?null;
          ??????}
          ????}

          可以看到是通過selectList來完成查詢多個和單個。

          ????@Override
          ????public??List?selectList(String?statement,?Object?parameter)?{
          ??????//?為了提供多種重載(簡化方法使用),和默認值
          ??????//?讓參數(shù)少的調(diào)用參數(shù)多的方法,只實現(xiàn)一次
          ??????return?this.selectList(statement,?parameter,?RowBounds.DEFAULT);
          ????}

          繼續(xù)看多參重載方法:

          ????@Override
          ????public??List?selectList(String?statement,?Object?parameter,?RowBounds?rowBounds)?{
          ??????try?{
          ????????MappedStatement?ms?=?configuration.getMappedStatement(statement);
          ????????//?如果?cacheEnabled?=?true(默認),Executor會被?CachingExecutor裝飾
          ????????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();
          ??????}
          ????}

          核心代碼就是executor.query,我們進去看看:

          ????@Override
          ????public??List?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler)?throws?SQLException?{
          ??????BoundSql?boundSql?=?ms.getBoundSql(parameter);
          ??????//?一級緩存和二級緩存的CacheKey是同一個
          ??????CacheKey?key?=?createCacheKey(ms,?parameter,?rowBounds,?boundSql);
          ??????return?query(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);
          ????}

          這里涉及到一級緩存和二級緩存,不是重點,我們就想看看最終是怎么執(zhí)行的jdbc操作,那么就只需要繼續(xù)看query重載。

          ????//?org.apache.ibatis.executor.BaseExecutor#query
          ????@Override
          ????public??List?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{

          ??????//?異常體系之?ErrorContext
          ??????ErrorContext.instance().resource(ms.getResource()).activity("executing?a?query").object(ms.getId());

          ??????if?(closed)?{
          ????????throw?new?ExecutorException("Executor?was?closed.");
          ??????}

          ??????if?(queryStack?==?0?&&?ms.isFlushCacheRequired())?{
          ????????//?flushCache="true"時,即使是查詢,也清空一級緩存
          ????????clearLocalCache();
          ??????}

          ??????List?list;
          ??????try?{

          ????????//?防止遞歸查詢重復(fù)處理緩存
          ????????queryStack++;
          ????????//?查詢一級緩存
          ????????//?ResultHandler?和?ResultSetHandler的區(qū)別
          ????????list?=?resultHandler?==?null???(List)?localCache.getObject(key)?:?null;
          ????????
          ????????if?(list?!=?null)?{
          ??????????handleLocallyCachedOutputParameters(ms,?key,?parameter,?boundSql);
          ????????}?else?{

          ??????????//?真正的查詢流程
          ??????????list?=?queryFromDatabase(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);

          ??????????...省略N行代碼...

          涉及到緩存的,通通與我無關(guān),只看真正的查詢流程 「queryFromDatabase」。

          ????private??List?queryFromDatabase(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{
          ??????List?list;
          ??????//?先占位
          ??????localCache.putObject(key,?EXECUTION_PLACEHOLDER);
          ??????try?{
          ????????//?三種?Executor?的區(qū)別,看doUpdate
          ????????//?默認Simple
          ????????list?=?doQuery(ms,?parameter,?rowBounds,?resultHandler,?boundSql);
          ??????}?finally?{
          ????????//?移除占位符
          ????????localCache.removeObject(key);
          ??????}
          ??????//?寫入一級緩存
          ??????localCache.putObject(key,?list);
          ??????if?(ms.getStatementType()?==?StatementType.CALLABLE)?{
          ????????localOutputParameterCache.putObject(key,?parameter);
          ??????}
          ??????return?list;
          ????}

          看到j(luò)dbc了,勝利的曙光。

          舒服,繼續(xù)看doQuery方法,看到resultHandler了么,結(jié)果處理器,感覺離結(jié)果更近了。

          ????//?org.apache.ibatis.executor.SimpleExecutor#doQuery
          ????@Override
          ????public??List?doQuery(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?BoundSql?boundSql)?throws?SQLException?{
          ??????Statement?stmt?=?null;
          ??????try?{
          ????????Configuration?configuration?=?ms.getConfiguration();
          ????????//?注意,已經(jīng)來到SQL處理的關(guān)鍵對象?StatementHandler?>>
          ????????StatementHandler?handler?=?configuration.newStatementHandler(wrapper,?ms,?parameter,?rowBounds,?resultHandler,?boundSql);
          ????????//?獲取一個?Statement對象
          ????????stmt?=?prepareStatement(handler,?ms.getStatementLog());
          ????????//?執(zhí)行查詢
          ????????return?handler.query(stmt,?resultHandler);
          ??????}?finally?{
          ????????//?用完就關(guān)閉
          ????????closeStatement(stmt);
          ??????}
          ????}

          查詢用的Exucutor就是默認的SimpleExecutor,看到了熟悉的prepareStatement獲取流程,基本上就到底層jdbc了。那么我們就看看 「prepareStatement(handler, ms.getStatementLog());」

          ????private?Statement?prepareStatement(StatementHandler?handler,?Log?statementLog)?throws?SQLException?{
          ??????Statement?stmt;
          ??????Connection?connection?=?getConnection(statementLog);
          ??????//?獲取?Statement?對象
          ??????stmt?=?handler.prepare(connection,?transaction.getTimeout());
          ??????//?為?Statement?設(shè)置參數(shù)
          ??????handler.parameterize(stmt);
          ??????return?stmt;
          ????}

          看到這里,就到j(luò)dbc層面了,我們看到了熟悉的Connection,獲取到connection之后再獲取Statement。

          這里的Statement就是java.sql的statement接口。

          ?

          org.apache.ibatis.executor.statement.SimpleStatementHandler#query

          ?
          ????@Override
          ????public??List?query(Statement?statement,?ResultHandler?resultHandler)?throws?SQLException?{
          ??????String?sql?=?boundSql.getSql();
          ??????statement.execute(sql);
          ??????return?resultSetHandler.handleResultSets(statement);
          ????}

          已經(jīng)獲取到了sql,通過Statement去執(zhí)行sql,再通過resultSetHandler處理結(jié)果集。

          通過Statement去執(zhí)行sql

          ??????statement.execute(sql);

          這里就已經(jīng)是jdbc層面的操作了,通過與數(shù)據(jù)庫建立的connection提交并執(zhí)行sql。

          通過resultSetHandler處理結(jié)果集

          ?

          都到最后了,我們也不慌了,那么就看看org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets是如何處理結(jié)果集的。

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

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

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

          ??????List?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???????????ResultMapping?parentMapping?=?nextResultMaps.get(resultSets[resultSetCount]);
          ??????????if?(parentMapping?!=?null)?{
          ????????????String?nestedResultMapId?=?parentMapping.getNestedResultMapId();
          ????????????ResultMap?resultMap?=?configuration.getResultMap(nestedResultMapId);
          ????????????//?在此處處理結(jié)果集
          ????????????handleResultSet(rsw,?resultMap,?null,?parentMapping);
          ??????????}
          ??????????rsw?=?getNextResultSet(stmt);
          ??????????cleanUpAfterHandlingResultSet();
          ??????????resultSetCount++;
          ????????}
          ??????}

          ??????return?collapseSingleResultList(multipleResults);
          ????}

          這么一坨代碼,只需要重點看

          ????handleResultSet(rsw,?resultMap,?null,?parentMapping);

          ????private?void?handleResultSet(ResultSetWrapper?rsw,?ResultMap?resultMap,?List?multipleResults,?ResultMapping?parentMapping)?throws?SQLException?{
          ??????try?{
          ????????if?(parentMapping?!=?null)?{
          ??????????handleRowValues(rsw,?resultMap,?null,?RowBounds.DEFAULT,?parentMapping);
          ????????}?else?{
          ??????????if?(resultHandler?==?null)?{
          ????????????DefaultResultHandler?defaultResultHandler?=?new?DefaultResultHandler(objectFactory);
          ????????????handleRowValues(rsw,?resultMap,?defaultResultHandler,?rowBounds,?null);
          ????????????multipleResults.add(defaultResultHandler.getResultList());
          ??????????}?else?{
          ????????????handleRowValues(rsw,?resultMap,?resultHandler,?rowBounds,?null);
          ??????????}
          ????????}
          ??????}?finally?{
          ????????//?issue?#228?(close?resultsets)
          ????????closeResultSet(rsw.getResultSet());
          ??????}
          ????}

          看看handleRowValues的邏輯 (有點心累),一鼓作氣再瞅兩眼。

          最終,來到了這個地方:

          ????//?org.apache.ibatis.executor.resultset.DefaultResultSetHandler
          ????//?????????#handleRowValuesForSimpleResultMap
          ????private?void?handleRowValuesForSimpleResultMap(ResultSetWrapper?rsw,?ResultMap?resultMap,?ResultHandler?resultHandler,?RowBounds?rowBounds,?ResultMapping?parentMapping)
          ????????throws?SQLException?{
          ??????DefaultResultContext?resultContext?=?new?DefaultResultContext<>();

          ??????//?看到了吧,沒什么好說的,就是jdbc的結(jié)果集處理
          ??????ResultSet?resultSet?=?rsw.getResultSet();

          ??????skipRows(resultSet,?rowBounds);
          ??????while?(shouldProcessMoreRows(resultContext,?rowBounds)?&&?!resultSet.isClosed()?&&?resultSet.next())?{
          ????????ResultMap?discriminatedResultMap?=?resolveDiscriminatedResultMap(resultSet,?resultMap,?null);
          ????????Object?rowValue?=?getRowValue(rsw,?discriminatedResultMap,?null);
          ????????storeObject(resultHandler,?resultContext,?rowValue,?parentMapping,?resultSet);
          ??????}
          ????}

          行了,不用再往下挖了,看到了熟悉的ResultSet獲取結(jié)果集的操作,Mybatis執(zhí)行sql的流程基本就結(jié)束了。

          底層還是熟悉的JDBC操作。

          小結(jié)

          其實寫了這么多,也沒啥想總結(jié)的,我們通過一個查詢操作,完整的把Mybatis從解析文件到執(zhí)行sql,再到結(jié)果集處理都從源碼級別剖析了一遍。

          那么我們回答一下開頭的問題:

          我只是寫了個Mapper接口,再配合xml或者注解,把SQL一寫,就可以執(zhí)行數(shù)據(jù)庫操作,這是為何?

          其實我們獲取到的Mapper對象,已經(jīng)是Mybatis幫我們生成的代理對象了,這個代理對象擁有與jdbc交互的一切必要條件。

          都說Mybatis是對JDBC的封裝,可是我卻看不到JDBC相關(guān)的接口和對象,它們到哪里去了?

          稍微往上翻翻,我們剛講了,實際上最底層就是封裝的jdbc的接口。

          我們看不到但是用到了,并且用起來還很爽,這就是封裝的魅力啊。

          為什么在Spring中使用Mybatis,不用加@Repository/@Component之類的注解,就可以隨用隨注入(如:@Autowired)?

          這個問題,就放到之后的文章講解吧,那么就敬請期待下一篇:Mybatis與Spring的愛情故事(從源碼層面解析,Mybatis是如何利用Spring擴展點,實現(xiàn)與Spring整合的。)

          最后,貼張圖,概括一下這個過程。圖是借來的,僅供學習討論,侵刪。

          ?

          創(chuàng)建會話工廠SqlSessionFactory

          ?

          flow/1.png
          ?

          創(chuàng)建會話SqlSession

          ?

          flow/2.png
          ?

          創(chuàng)建代理對象

          ?

          flow/3.png
          ?

          調(diào)用代理對象,執(zhí)行SQL流程

          ?

          flow/4.png

          那么,不見不散。

          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    九九久久精品 | 日韩淫秽视频 | 久久夜色精品国产噜噜亚洲AV | 操操操网站 | 人人干人人摸人人草 |