• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • Mybatis源碼學習(15)-binding模塊之MapperMethod類

    標簽: MapperMethod  SqlCommand  MethodSignature  binding  Mybatis

    一、概述
    1、Mybatis操作數據

    ??在Mybatis中,進行數據操作時,有兩種方式,分別是:1、使用傳統的MyBatis提供的API;2、使用Mapper接口,即面向接口編程。

    1. 傳統的Mybatis工作模式
      示例:
        @Test
    	public void testApi() {
    		SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自動提交
    		Map<String, Object> param = new HashMap<String, Object>();
    		param.put("field1", "a1");
    		List<Map<String, Object>> list = sqlSession.selectList("TestMapper.queryList", param);
    		System.out.println(list.toString());
    	}
    

    在這里插入圖片描述
    2. 面向接口編程
    示例:

    @Test
    	public void testMapper() {
    		SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自動提交
    		TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
    		Map<String, Object> param = new HashMap<String, Object>();
    		param.put("field1", "a1");
    		List<Map<String, Object>> list = testMapper.queryList(param);
    		System.out.println(list.toString());
    	}
    

    在這里插入圖片描述

    2、面向接口編程簡介

    ??Mybatis的binding模塊就是用來實現面向接口編程的功能。面向接口編程,其實就是通過建立Mapper接口與XML配置文件的一一對應關系,從而實現通過操作對應接口來實現操作數據庫SQL語句實現增刪改查功能。

    二、binding模塊結構

    Mybatis的binding模塊的包目錄:org.apache.ibatis.binding。具體包結構如下圖所示:
    在這里插入圖片描述
    其中,Mybatis的binding模塊就是用到了動態代理。

    • MapperMethod
      定義Mapper接口中對應方法的信息以及對應SQL語句的信息,用來完成參數轉換以及SQL語句的執行功能,可以看成連接Mapper接口以及映射配置文件中定義的SQL語句的橋梁。
    • MapperProxy
      MapperProxy實現了lnvocationHandler接口,是代理對象。
    • MapperProxyFactory
      負責創建代理對象
    • MapperRegistry
      Mapper接口及其對應的代理對象工廠的注冊中心
    三、MapperMethod類

    ??定義Mapper接口中對應方法的信息以及對應SQL語句的信息,用來完成參數轉換以及SQL語句的執行功能,可以看成連接Mapper接口以及映射配置文件中定義的SQL語句的橋梁。

    1. 字段、構造函數
      在MapperMethod類中,定義了command、method兩個字段,這兩個字段均是內部類對象。其中command定義SQL命令的概要信息,主要包括了SQL命令的名稱和類型;method定義Mapper接口中方法的相關信息。構造函數主要是實現command、method兩個對象的初始化。
      private final SqlCommand command; //定義SQL命令的概要信息,主要包括了SQL命令的名稱和類型
      private final MethodSignature method;//定義Mapper接口中方法的相關信息
    
      public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
      }
    
    1. 內部類 SqlCommand
      MapperMethod內部類,用來定義SQL命令的概要信息,主要包括了SQL命令的名稱和類型。
     public static class SqlCommand {
    
        private final String name;//定義SQL命令的名稱,由Mapper接口的全限定類名與對應的方法名稱組成的。
        private final SqlCommandType type;//定義SQL命令的類型,SqlCommandType是枚舉類型,可選值為:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
    
        /**
         * 構造函數,完成對字段屬性name、type的初始化。
         * 同時驗證了Configuration.mappedStatements中是否有對應的MappedStatement對象(在解析XML時初始化該對象)。
         * @param configuration
         * @param mapperInterface
         * @param method
         */
        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
          final String methodName = method.getName();
          final Class<?> declaringClass = method.getDeclaringClass();
          MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
              configuration);
          if (ms == null) {
            if (method.getAnnotation(Flush.class) != null) {
              name = null;
              type = SqlCommandType.FLUSH;
            } else {
              throw new BindingException("Invalid bound statement (not found): "
                  + mapperInterface.getName() + "." + methodName);
            }
          } else {
            name = ms.getId();
            type = ms.getSqlCommandType();
            if (type == SqlCommandType.UNKNOWN) {
              throw new BindingException("Unknown execution method for: " + name);
            }
          }
        }
    
        public String getName() {
          return name;
        }
    
        public SqlCommandType getType() {
          return type;
        }
        /**
         * 解析SqlCommand實例對應的MappedStatement對象,并返回。
         * 對應的MappedStatement對象在解析Mapper對應的XML時,進行初始化,并存儲在了Configuration.mappedStatements中。
         * @param mapperInterface
         * @param methodName
         * @param declaringClass
         * @param configuration
         * @return
         */
        private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
            Class<?> declaringClass, Configuration configuration) {
          String statementId = mapperInterface.getName() + "." + methodName;
          if (configuration.hasStatement(statementId)) {
            return configuration.getMappedStatement(statementId);
          } else if (mapperInterface.equals(declaringClass)) {//當declaringClass==mapperInterface時,說明methodName對應方法的MappedStatement對象不存在,且不可能是在在超類中,所以直接返回null。
            return null;
          }
          //下面循環是處理當methodName對應的方法在超類中的情況
          for (Class<?> superInterface : mapperInterface.getInterfaces()) {
            if (declaringClass.isAssignableFrom(superInterface)) {//判斷declaringClass是superInterface的父類,如果是繼續查找對應的父類,直到查找到對應的MappedStatement或者檢索完所有的父類
              MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                  declaringClass, configuration);
              if (ms != null) {
                return ms;
              }
            }
          }
          return null;
        }
      }
    
    1. 內部類 MethodSignature
      定義Mapper接口中方法的相關信息。其中ParamNameResolver類后續再詳細分析,現在只需要了解是用來處理方法參數即可。
    public static class MethodSignature {
    
        private final boolean returnsMany;//返回值類型是否為Collection類型或是數組類型
        private final boolean returnsMap;//返回值類型是否為Map類型
        private final boolean returnsVoid;//返回值類型是否為void類型
        private final boolean returnsCursor;//返回值是否為Cursor類型
        private final Class<?> returnType;//返回值類型
        private final String mapKey;//如果返回值類型是Map,則該字段記錄了作為key的列名
        private final Integer resultHandlerIndex;//用來標記該方法參數列表中ResultHandler類型參數的位置
        private final Integer rowBoundsIndex;//用來標記該方法參數列表中RowBounds類型參數的位置
        private final ParamNameResolver paramNameResolver;//該方法對應的ParamNameResolver對象,主要用來處理方法的參數
    
        public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
          Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
          if (resolvedReturnType instanceof Class<?>) {
            this.returnType = (Class<?>) resolvedReturnType;
          } else if (resolvedReturnType instanceof ParameterizedType) {
            this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
          } else {
            this.returnType = method.getReturnType();
          }
          this.returnsVoid = void.class.equals(this.returnType);
          this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
          this.returnsCursor = Cursor.class.equals(this.returnType);
          this.mapKey = getMapKey(method);
          this.returnsMap = this.mapKey != null;
          this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
          this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
          this.paramNameResolver = new ParamNameResolver(configuration, method);
        }
        /**
         * 負責將args[]數組(用戶傳入的實參列表)轉換成SQL語句對應的參數列表
         * @param args
         * @return
         */
        public Object convertArgsToSqlCommandParam(Object[] args) {
          return paramNameResolver.getNamedParams(args);
        }
    
        public boolean hasRowBounds() {
          return rowBoundsIndex != null;
        }
    
        public RowBounds extractRowBounds(Object[] args) {
          return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
        }
    
        public boolean hasResultHandler() {
          return resultHandlerIndex != null;
        }
    
        public ResultHandler extractResultHandler(Object[] args) {
          return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
        }
    
        public String getMapKey() {
          return mapKey;
        }
    
        public Class<?> getReturnType() {
          return returnType;
        }
    
        public boolean returnsMany() {
          return returnsMany;
        }
    
        public boolean returnsMap() {
          return returnsMap;
        }
    
        public boolean returnsVoid() {
          return returnsVoid;
        }
    
        public boolean returnsCursor() {
          return returnsCursor;
        }
        /**
         * 查找指定類型的參數在參數列表中的位置,且該paramType在參數中只能有一個,否則會拋出BindingException異常。
         * @param method
         * @param paramType
         * @return
         */
        private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
          Integer index = null;
          final Class<?>[] argTypes = method.getParameterTypes();
          for (int i = 0; i < argTypes.length; i++) {
            if (paramType.isAssignableFrom(argTypes[i])) {
              if (index == null) {
                index = i;
              } else {
                throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
              }
            }
          }
          return index;
        }
    
        private String getMapKey(Method method) {
          String mapKey = null;
          if (Map.class.isAssignableFrom(method.getReturnType())) {
            final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
            if (mapKeyAnnotation != null) {
              mapKey = mapKeyAnnotation.value();
            }
          }
          return mapKey;
        }
      }
    
    1. 內部類 ParamMap
      重寫了HashMap類,主要重寫了get()方法,在原來的基礎上,添加了當獲取不存在的key值時,直接保存BindingException異常。
    public static class ParamMap<V> extends HashMap<String, V> {
    
        private static final long serialVersionUID = -2212268410512043556L;
    
        @Override
        public V get(Object key) {
          if (!super.containsKey(key)) {
            throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
          }
          return super.get(key);
        }
    
      }
    
    1. execute()方法
      代理對象調用invoke()方法時,最終是調用了execute()方法,是最核心的代碼,是實現操作SQL語句的邏輯的地方。這個方法主要用來處理參數、處理返回結果,真正操作數據的操作還是交給了sqlSession對應的方法。
    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);
              result = sqlSession.selectOne(command.getName(), param);
            }
            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;
      }
    
    1. convertArgsToSqlCommandParam()方法
      負責將args[]數組(用戶傳入的實參列表)轉換成SQL語句對應的參數列表。內部是通過paramNameResolver的getNamedParams()方法實現。
     public Object convertArgsToSqlCommandParam(Object[] args) {
          return paramNameResolver.getNamedParams(args);
        }
    
    1. 返回結果處理
    /**
       * 對數字或布爾類型的返回結果進行轉換
       * @param rowCount
       * @return
       */
      private Object rowCountResult(int rowCount) {
        final Object result;
        if (method.returnsVoid()) {
          result = null;
        } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
          result = rowCount;
        } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
          result = (long)rowCount;
        } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
          result = rowCount > 0;
        } else {
          throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
        }
        return result;
      }
      /**
       * 處理需要通過回調ResultHandler處理結果集的情況
       * @param sqlSession
       * @param args
       */
      private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
        MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
        if (!StatementType.CALLABLE.equals(ms.getStatementType())
            && void.class.equals(ms.getResultMaps().get(0).getType())) {
          throw new BindingException("method " + command.getName()
              + " needs either a @ResultMap annotation, a @ResultType annotation,"
              + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
        }
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
          RowBounds rowBounds = method.extractRowBounds(args);
          sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
        } else {
          sqlSession.select(command.getName(), param, method.extractResultHandler(args));
        }
      }
      /**
       * 處理對應方法的返回值為數組或是Collection接口實現類
       * @param <E>
       * @param sqlSession
       * @param args
       * @return
       */
      private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
          RowBounds rowBounds = method.extractRowBounds(args);
          result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
        } else {
          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;
      }
      /**
       * 處理返回值為Cursor的方法
       * @param <T>
       * @param sqlSession
       * @param args
       * @return
       */
      private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
        Cursor<T> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
          RowBounds rowBounds = method.extractRowBounds(args);
          result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
        } else {
          result = sqlSession.<T>selectCursor(command.getName(), param);
        }
        return result;
      }
      /**
       * 主要負責將結果對象轉換成Collection集合對象和數組對象
       * @param <E>
       * @param config
       * @param list
       * @return
       */
      private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
        Object collection = config.getObjectFactory().create(method.getReturnType());
        MetaObject metaObject = config.newMetaObject(collection);
        metaObject.addAll(list);
        return collection;
      }
      /**
       * 主要負責將結果對象轉換成Collection集合對象和數組對象
       * @param <E>
       * @param list
       * @return
       */
      @SuppressWarnings("unchecked")
      private <E> Object convertToArray(List<E> list) {
        Class<?> arrayComponentType = method.getReturnType().getComponentType();
        Object array = Array.newInstance(arrayComponentType, list.size());
        if (arrayComponentType.isPrimitive()) {
          for (int i = 0; i < list.size(); i++) {
            Array.set(array, i, list.get(i));
          }
          return array;
        } else {
          return list.toArray((E[])array);
        }
      }
      /**
       * 主要負責將結果對象轉換成Map對象
       * @param <K>
       * @param <V>
       * @param sqlSession
       * @param args
       * @return
       */
      private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
        Map<K, V> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
          RowBounds rowBounds = method.extractRowBounds(args);
          result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
        } else {
          result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
        }
        return result;
      }
    
    版權聲明:本文為hou_ge原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/hou_ge/article/details/102629056

    智能推薦

    mybatis源碼解析之基礎模塊-Transaction

    MyBatis源碼解析之基礎模塊—Transaction 前文回顧 上一篇,咱們一起學習了Mybatis的DataSource模塊相關源碼,掌握了三種數據源工廠的邏輯,同時也掌握非池化數據源的連接創建,池化數據源如何從空閑列表獲取連接并放到活躍連接列表,及連接的歸還到空閑隊列的邏輯。 下面跟隨筆者的思路,咱們繼續學習另一個重要模塊——Transaction模塊。...

    mybatis源碼解析之基礎模塊-log

    MyBatis源碼解析之基礎模塊—Log 前文回顧 上一章節我們一起學習了DataSource源碼邏輯。本次我們學習MyBatis的Log日志處理模塊。 背景描述 只要做過技術開發的小伙伴都應該明白日志的重要性。這是用于追蹤線上運行情況及排查問題的利器。如果沒有有效規范的日志輸出,碰到問題特別是線上問題將會陷入一片迷茫,而且線上環境又不能隨意調整。而日志中很重要的一部分還是與數據變更相...

    mybatis源碼解析之基礎模塊-Plugin

    MyBatis源碼解析之基礎模塊—Plugin 前文回顧 上一章節我們一起學習了Mapper接口綁定的源碼邏輯。本次我們學習MyBatis的Plugin數據源模塊。 架構設計 Plugin模塊所在包路徑為org.apache.ibatis.plugin,對應的類架構設計圖如下: 源碼解讀 Signature Signature注解類主要定義了三個屬性,通過這些屬性定位對應要攔截的方法。...

    mybatis源碼解析之基礎模塊-TypeHandler

    MyBatis源碼解析之基礎模塊—TypeHandler 前文回顧 上一章節我們一起分析了Mybatis的Plugin模塊的源碼。掌握了如何配置攔截器注解,如何自定義攔截器以及攔截器的執行過程。 在使用Mybatis的過程中,基本上我們都要在xml中編寫相應的sql語句以及對應的java屬性與字段的轉換。那么對于數據庫與java之間的轉換,Mybatis是怎么做的呢? 接下來本章節我們...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    猜你喜歡

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    精品国产乱码久久久久久蜜桃不卡