<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      從源碼的角度弄懂MyBatis動態代理開發原理

      MyBatis提供了一種動態代理實現SQL調用的功能,使用者只需要在映射文件中配置SQL語句與映射規則即可完成SQL調用和結果集封裝。下面代碼展示了動態代理調用的基本步驟:

      public void testMyBatisBuild() throws IOException {
          InputStream input = Resources.getResourceAsStream("SqlSessionConfig.xml");
          SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(input);
          SqlSession sqlSession = sessionFactory.openSession();
          TestMapper mapper = sqlSession.getMapper(TestMapper.class);
          Student student = mapper.getStudentByIdToResultType("00000b373502481baa1a5f5229507cf8");
          System.out.println(student);
      }
      

      而我們通過getMapper方法只是傳入了一個接口,在整個項目中我們沒有一個TestMapper的實現類,那MyBatis是如何幫我們生成實現類的,這一點就需要我們去分析源碼了。

      一. DefaultSqlSessionFactory

      SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(input);
      

      build方法用于解析配置文件并生成SqlSessionFactory,我們通過跟蹤build方法的源碼,我們可以發現,build方法實際上返回的是DefaultSqlSessionFactory的實例。DefaultSqlSessionFactory就是SqlSessionFactory接口的唯一實現類。

      public SqlSessionFactory build(InputStream inputStream, String environment) {
          return build(inputStream, environment, null);
       }
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
          try {
            //創建XMLConfigBuilder,用于解析配置文件
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            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.
            }
          }
        }
      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
      

      `

      二. DefaultSqlSession

      2.1 openSession()

      SqlSession sqlSession = sessionFactory.openSession();
      

      上面代碼是通過SqlSessionFactory獲取SqlSession的代碼,我們進入DefaultSqlSessionFactory::openSession方法一探究竟,看看它底層到底做了什么。

      可以看到DefaultSqlSessionFactory::openSession方法最終生成了一個DefaultSqlSession實例,它就是Mybatis核心接口SqlSession的實現類。

      public SqlSession openSession() {
        //從數據源中獲取連接,然后創建SqlSessionFactory
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }
      private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          //獲取mybatis-config.xml中的enviroment對象
          final Environment environment = configuration.getEnvironment();
          //從Enviroment獲取TranslationFactory
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          //從數據源中獲取數據庫連接,然后創建Transaction對象
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          //重點:根據配置創建Executor,該方法內部會根據用戶是否配置二級緩存去決定是否創建二級緩存的裝飾器去裝飾Executor,這也是二級緩存是否生效的關鍵
          final Executor executor = configuration.newExecutor(tx, execType);
          //創建DefaultSqlSession
          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();
        }
      }
      

      2.2 getMapper()獲取動態代理實例

      TestMapper mapper = sqlSession.getMapper(TestMapper.class);
      

      走到這里我們就來到今天主題的核心,我們現在就來抽絲剝繭般的看看MyBatis是如何幫我們生成動態代理實現類的。

      首先我們進入DefaultSqlSession::getMapper方法,可以看到實際上它是調用的Configuration::getMapper方法獲取的代理實例:

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

      Configuration是MyBatis初始化后全局唯一的配置對象,它內部保存著配置文件解析過程中所有的配置信息。進入Configuration::getMapper我們可以發現它實際上調用的是MapperRegistry::getMapper方法:

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //調用mapper注冊中心的getMapper方法獲取Mapper動態代理實現類
        return mapperRegistry.getMapper(type, sqlSession);
      }
      

      MapperRegistry是Mapper接口動態代理工廠類的注冊中心,我們繼續進入MapperRegistry::getMapper方法,可以看到它實際上調用的是MapperProxyFactory::newInstance方法。

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
        	//使用Mapper代理工廠創建動態代理實例
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
      
      

      MapperProxyFactory是生成動態代理對象的工廠類,走到這里,我有一種預感,我們離真相越來越近了。我們進入MapperProxyFactory::newInstance一探究竟:

      public T newInstance(SqlSession sqlSession) {
          //MapperProxy實現了InvocationHandler接口。它是Mapper動態代理的核心類
          final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
          //創建動態代理實例
      	return newInstance(mapperProxy);
        }
      
      

      我們先暫時略過MapperProxy內部的處理流程,我們先看看newInstance方法內部是如何創建動態代理實例的。

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

      看到上面的代碼是不是有一種恍然大悟的感覺,它是JDK動態代理的核心API,也就是說Mybatis底層是調用JDK的Proxy類來創建代理實例。對JDK動態代理不熟悉的小伙伴可以看看博主的另一篇文章:JDK動態代理的深入理解

      三. 動態實例是如何執行的

      代理實例如何創建的過程我們已經清楚了,現在我們需要了解代理類內部是如何實現SQL語句的執行的。我們進入MapperProxy::invoke方法:

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            //如果是Object方法,則調用方法本身
            return method.invoke(this, args);
          } else {
            //根據被調用接口方法的Method對象,從緩存中獲取MapperMethodInvoker對象,如果沒有則創建一個并放入緩存,然后調用invoke。
            //換句話說,Mapper接口中的每一個方法都對應一個MapperMethodInvoker對象,而MapperMethodInvoker對象里面的MapperMethod保存著對應的SQL信息和返回類型以完成SQL調用
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
      
      

      看了看invoke方法內部,似乎并沒有看出真實的調用邏輯,那我們就先進入cacheInvoker方法中看看吧:

      /**
        * 獲取緩存中MapperMethodInvoker,如果沒有則創建一個,而MapperMethodInvoker內部封裝這一個MethodHandler
        * @param method
        * @return
        * @throws Throwable
        */
       private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
         try {
           return methodCache.computeIfAbsent(method, m -> {
             if (m.isDefault()) {
               //如果調用接口的是默認方法(JDK8新增接口默認方法的概念)
               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 {
               //如果調用的普通方法(非default方法),則創建一個PlainMethodInvoker并放入緩存,其中MapperMethod保存對應接口方法的SQL以及入參和出參的數據類型等信息
               return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
             }
           });
         } catch (RuntimeException re) {
           Throwable cause = re.getCause();
           throw cause == null ? re : cause;
         }
       }
      
      

      可以看到進入cacheInvoker方法后首先會判斷用戶當前調用的是否是接口的default方法,如果不是就會創建一個PlainMethodInvoker對象并返回。

      PlainMethodInvoker:類是Mapper接口普通方法的調用類,它實現了MethodInvoker接口。其內部封裝了MapperMethod實例。

      MapperMethod:封裝了Mapper接口中對應方法的信息,以及對應的SQL語句的信息;它是mapper接口與映射配置文件中SQL語句的橋梁。

      此時我們跳出cachedInvoker方法回到MapperProxy::invoke方法中。

       return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      
      

      我們可以看到當cacheInvoker返回了PalinMethodInvoker實例之后,緊接著調用了這個實例的PlainMethodInvoker::invoke方法。進入PlainMethodInvoker::invoke方法我們發現它底層調用的是MapperMethod::execute方法:

      @Override
      public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
        //Mybatis如何幫助用戶實現動態代理的玄機就在里面
        return mapperMethod.execute(sqlSession, args);
      }
      
      

      進入MapperMethod::invoke方法我們會發現眼前一亮,這就是MyBatis底層動態代理的邏輯,可以看到動態代理最后還是使用SqlSession操作數據庫的:

        */
        public Object execute(SqlSession sqlSession, Object[] args) {
          Object result;
          switch (command.getType()) {
            case INSERT: {
              // 將args進行解析,如果是多個參數則,則根據@Param注解指定名稱將參數轉換為Map,如果是封裝實體則不轉換
              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 {
                //解析參數,因為SqlSession::selectOne方法參數只能傳入一個,但是我們Mapper中可能傳入多個參數,
                //有可能是通過@Param注解指定參數名,所以這里需要將Mapper接口方法中的多個參數轉化為一個ParamMap,
                //也就是說如果是傳入的單個封裝實體,那么直接返回出來;如果傳入的是多個參數,實際上都轉換成了Map
                Object param = method.convertArgsToSqlCommandParam(args);
                //可以看到動態代理最后還是使用SqlSession操作數據庫的
                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;
        }
      
      

      四. 動態代理調用過程時序圖

      在這里插入圖片描述

      由于圖片版幅較大,網頁顯示字體極小,這里博主給出下載鏈接供大家下載:MyBatis核心調用流程時序圖-GitHubMyBatis核心調用流程時序圖-Gitee

      五. 總結

      Mapper動態代理是通過MyBatis生成接口的實現類,然后調用SqlSession中的方法執行數據庫操作。上文只是對MyBatis這個過程源碼的簡單分析,希望讀者在讀后能夠明白Mapper動態代理的幾個核心問題:

      • MyBatis如何生成動態代理實例的?
      • MyBatis如何根據不同的情況將Mapper接口“翻譯”成SqlSession調用的
        • 如何確定調用SqlSession中的那個方法?
        • 如何確定命名空間和調用的statementId
        • 如何傳遞參數給SqlSession
      • 從源碼的角度看Mapper代理實例和SqlSession是一對一的關系,而SqlSession是線程不安全的,那么在Spring和MyBatis集成的時候,項目的整個生命周期中Mapper接口是單例的(通常情況),那么MyBatis是如何解決SqlSession線程安全問題的?(這個問題在這個部分的源碼分析中暫時得不到解決)

      最后,博主自己對MyBatis源碼進行了詳細注釋,如有需要,請移步至:GitHubGitee

      本文闡述了自己對MyBatis源碼的一些理解,如有不足,歡迎大佬指點,感謝感謝!!

      posted @ 2020-04-30 15:35  聽到微笑  閱讀(75)  評論(0)    收藏  舉報  來源
      主站蜘蛛池模板: 中文字幕亚洲综合第一页| 欧美极品少妇×xxxbbb| 婷婷综合亚洲| 亚洲三区在线观看内射后入| 日韩精品在线观看一二区| 大英县| 日本一区二区三区东京热| 亚洲一二三区精品美妇| 国产精品福利自产拍久久| 麻豆久久久9性大片| 日韩精品国产二区三区| 无码专区人妻系列日韩精品少妇| 男女激情一区二区三区| 国内在线视频一区二区三区| 国产精品久久久久影院| 亚洲成av人片天堂网无码| 亚洲中文字幕在线无码一区二区| 成人做爰视频www| av日韩在线一区二区三区| 午夜通通国产精品福利| 亚洲精品在线二区三区| 欧美成人精品高清在线播放| 欧美性XXXX极品HD欧美风情| 亚洲人成电影网站 久久影视| 香港日本三级亚洲三级| 一面膜上边一面膜下边视频| 久久综合88熟人妻| 亚洲av乱码久久亚洲精品| 国产午夜福利视频合集| 欧美性色黄大片www喷水| 丰满少妇在线观看网站| 国产成人8X人网站视频| 国产无遮挡性视频免费看| 色综合久久中文综合久久激情 | 亚洲精品tv久久久久久久久久| 日韩有码中文字幕av| 亚洲中文字幕在线二页| 色婷婷五月综合久久| 国产欧美精品aaaaaa片 | 四虎在线播放亚洲成人| 久久99久国产精品66|