• <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源碼解析之基礎模塊—binding

    標簽: mybatis源碼學習  mybatis

    在這里插入圖片描述

    MyBatis源碼解析之基礎模塊—binding

    binding未誕生之暗黑時代

    在介紹MyBatis的binding之前,咱們先一段代碼:

    UserInfoDAO

    package com.todobugs.study.dao;
    
    import com.todobugs.study.domain.UserInfo;
    import com.todobugs.study.query.UserInfoQuery;
    
    public interface UserInfoDAO {
        Long insert(UserInfo userInfo);
        /**
         * 根據用戶名查找
         * @param userName
         * @return
         */
        UserInfo selectUserInfoByName(String userName);
    }
    

    UserInfoDaoImpl

    package com.todobugs.study.dao.impl;
    
    import com.todobugs.study.dao.BaseDAO;
    import com.todobugs.study.dao.UserInfoDAO;
    import com.todobugs.study.domain.UserInfo;
    import org.springframework.stereotype.Repository;
    
    @Repository("userInfoDAO")
    public class UserInfoDAOImpl extends BaseDAO implements UserInfoDAO {
      
        private static final String SQLMAP_SPACE = "USER_INFO.";
    
        public Long insert(UserInfo userInfo) {
            return (Long)getSqlMapClientTemplate().insert(SQLMAP_SPACE + "insert", userInfo);
        }
    
    	@Override
    	public UserInfo selectUserInfoByName(String userName) {
    		return (UserInfo) this.getSqlMapClientTemplate().queryForObject(SQLMAP_SPACE+"getByName", userName);
    	}
    }
    

    上述兩份源碼就是使用ibatis開發的dao,從中可以看出dao實現類其實沒有什么業務邏輯處理,就是為了綁定namespace 及sql節點。

    在ibatis時代,開發者在編寫dao(即現在的mapper)時必須要實現該dao接口,其根本目的只是指定對應的namespace及操作節點。雖然實現內容很簡單,這給開發者帶來不必要且繁瑣的編碼,且在編譯時并不能發現開發者是否存在異常,只有在運行時才能發現。

    為解決這種操作方式的弊端,在mybatis版本中提供了binding模塊。從而能夠在編譯期就能夠發現問題。同時通過采用jdk動態代理模式,開發者只需要要編寫對應的接口即可完成持久層的開發工作。即降低工作量,有大大降低出錯概率。

    接下來,我們將通過源碼詳細介紹binding的執行邏輯。

    架構設計

    binding模塊所在包路徑為org.apache.ibatis.binding,類關系比較簡單,總共就五個類:

    • MapperRegistry:Mapper注冊類
    • MapperProxyFactory:Mapper代理工廠類
    • MapperProxy:Mapper代理類
    • MapperMethod:Mapper執行方法
    • BindingException:綁定異常類()

    其類之間的架構設計關系為:

    在這里插入圖片描述

    接下來各類中主要方法依次介紹。

    源碼解讀

    MapperRegistry

    老規矩,先上源碼:

    package org.apache.ibatis.binding;
    
    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
    import org.apache.ibatis.io.ResolverUtil;
    import org.apache.ibatis.session.Configuration;
    import org.apache.ibatis.session.SqlSession;
    
    
    public class MapperRegistry {
      /** 全局配置類 */
      private final Configuration config;
      /** 已添加的mapper代理類工廠 */
      private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
      /** 構造函數 */
      public MapperRegistry(Configuration config) {
        this.config = config;
      }
    
      /** 根據包名添加mapper */
      public void addMappers(String packageName) {
        //默認superType為Object.class,這樣該包下的所有接口均會被添加到knownMappers中
        addMappers(packageName, Object.class);
      }
    
      /** 根據指定包名及父類類型添加mapper */
      public void addMappers(String packageName, Class<?> superType) {
        /** 通過resolverUtil類判斷查詢packageName包下所有匹配superType的類型,并添加到一個set類型集合中 */
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        /** 循環遍歷該集合,并將該mapperClass添加到knownMappers中 */
        for (Class<?> mapperClass : mapperSet) {
          addMapper(mapperClass);
        }
      }
    
      /**
       * 判斷是否為接口,是的話才會生成代理對象
       * 后續會在運行時該代理對象會被攔截器進行攔截處理
       */
      public <T> void addMapper(Class<T> type) {
        /** 1、判斷傳入的type是否是一個接口
         *  2、判斷knownMappers是否已經存在,若存在則拋出已存在異常。
         *  3、設置是否加載完成標識,final會根據是否加載完成來區別是否刪除該type的設置
         *  4、將該接口put到knownMappers中
         *  5、調用MapperAnnotationBuilder構造方法,并進行解析。(具體處理邏輯會在builder模塊中展開)
         */
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    
      /** 獲取mapper代理對象 */
      @SuppressWarnings("unchecked")
      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 {
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
    
      /**判斷knownMappers中是否存在該類型的mapper代理工廠*/
      public <T> boolean hasMapper(Class<T> type) {
        return knownMappers.containsKey(type);
      }
    
      /** 主要用于測試,無需關注 */
      public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(knownMappers.keySet());
      }
    }
    

    在Configuration實例化時,會通過如下方式進行實例化。

    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    

    從源碼中可以看出,MapperRegister類只有兩個屬性,七個方法(包含一個構造方法)

    • config:該屬性里面包含各種mybatis的配置信息,此處不再贅述。
    • knownMappers:該屬性存放mapper接口并提供其代理對象(稍后介紹MapperProxyFactory)。
    • MapperRegistry:該構造方法注入config配置信息。
    • addMappers(String packageName):根據包名添加該包下的所有mapper接口類,調用下述重載方法
    • addMappers(String packageName, Class<?> superType):重載方法,根據包名及父類類型添加該包下的所有mapper接口類
    • getMapper:根據mapper類型及sqlsession獲取對應mapper接口的代理對象。
    • hasMapper:根據mapper類型判斷knownMappers中是否已存在,主要用于addMapper時校驗。
    • addMapper:根據mapper類型將其添加到knownMappers中,該方法默認被addMappers(String packageName, Class<?> superType)循環調用,開發者亦可手動調用。

    MapperProxyFactory

    package org.apache.ibatis.binding;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.apache.ibatis.binding.MapperProxy.MapperMethodInvoker;
    import org.apache.ibatis.session.SqlSession;
    
    public class MapperProxyFactory<T> {
      /** mapper接口 */
      private final Class<T> mapperInterface;
      /** method緩存 */
      private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
      /** 構造方法 */
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
      /** 獲取mapper接口 */
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
      /** 獲取method緩存 */
      public Map<Method, MapperMethodInvoker> getMethodCache() {
        return methodCache;
      }
    
      /** 創建代理對象 */
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
      /** 重載方法創建代理對象 */
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    }
    

    MapperProxyFactory代碼比較簡單,持有mapperInterface,methodCache兩個屬性,一個構造方法(參數為mapper接口),兩個獲取代理對象的newInstance方法:

    • MapperProxyFactory(Class mapperInterface) 構造方法在執行MapperRegister#addMapper時添加到knownMappers的過程中進行實例化調用

      knownMappers.put(type, new MapperProxyFactory<>(type));
      
    • getMethodCache() :獲取methodCache信息,該methodCache在調用cachedInvoker時進行填充。

    • newInstance(SqlSession sqlSession):通過sqlSession創建MappserProxy代理對象實例。

    • newInstance(MapperProxy mapperProxy):根據mapperProxy代理對象實例化代理對象(有點繞)

    MapperProxy

    package org.apache.ibatis.binding;
    
    import java.io.Serializable;
    import java.lang.invoke.MethodHandle;
    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodHandles.Lookup;
    import java.lang.invoke.MethodType;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Map;
    
    import org.apache.ibatis.reflection.ExceptionUtil;
    import org.apache.ibatis.session.SqlSession;
    
    public class MapperProxy<T> implements InvocationHandler, Serializable {
    
      private static final long serialVersionUID = -4724728412955527868L;
      private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
          | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
      private static final Constructor<Lookup> lookupConstructor;
      private static final Method privateLookupInMethod;
      private final SqlSession sqlSession;
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethodInvoker> methodCache;
    
      /** MapperProxy構造方法,被MapperProxyFactory調用用于實例化代理對象 */
      public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
      }
    
      /** 靜態代碼塊初始化合適的MethodHandler */
      static {
        Method privateLookupIn;
        try {
          privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
        } catch (NoSuchMethodException e) {
          privateLookupIn = null;
        }
        privateLookupInMethod = privateLookupIn;
    
        Constructor<Lookup> lookup = null;
        if (privateLookupInMethod == null) {
          // JDK 1.8
          try {
            lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
            lookup.setAccessible(true);
          } catch (NoSuchMethodException e) {
            throw new IllegalStateException(
                "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
                e);
          } catch (Exception e) {
            lookup = null;
          }
        }
        lookupConstructor = lookup;
      }
      /** 調用(根據method的類型聲明判斷方法調用類型) */
      @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 {
            return cachedInvoker(proxy, method, args).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
      /** 緩存調用 */
      private MapperMethodInvoker cachedInvoker(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          return methodCache.computeIfAbsent(method, m -> {
            if (m.isDefault()) {
              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 {
              return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
          });
        } catch (RuntimeException re) {
          Throwable cause = re.getCause();
          throw cause == null ? re : cause;
        }
      }
      /** jdk1.9下調用 */
      private MethodHandle getMethodHandleJava9(Method method)
          throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final Class<?> declaringClass = method.getDeclaringClass();
        return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
            declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
            declaringClass);
      }
      /** jdk1.8下調用 */
      private MethodHandle getMethodHandleJava8(Method method)
          throws IllegalAccessException, InstantiationException, InvocationTargetException {
        final Class<?> declaringClass = method.getDeclaringClass();
        return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
      }
    
      /** 內部接口MapperMethodInvoker */
      interface MapperMethodInvoker {
        Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
      }
    
      /** MapperMethodInvoker接口PlainMethodInvoker實現 */
      private static class PlainMethodInvoker implements MapperMethodInvoker {
        private final MapperMethod mapperMethod;
    
        public PlainMethodInvoker(MapperMethod mapperMethod) {
          super();
          this.mapperMethod = mapperMethod;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          /** 調用MapperMethod中的excute方法 */
          return mapperMethod.execute(sqlSession, args);
        }
      }
    
      /** MapperMethodInvoker接口 DefaultMethodInvoker 實現 */
      private static class DefaultMethodInvoker implements MapperMethodInvoker {
        private final MethodHandle methodHandle;
    
        public DefaultMethodInvoker(MethodHandle methodHandle) {
          super();
          this.methodHandle = methodHandle;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return methodHandle.bindTo(proxy).invokeWithArguments(args);
        }
      }
    }
    

    MapperProxy代碼較多,但主要功能還是比較清晰簡單

    • 首先因項目運行環境的jdk可能不同,在啟動時會通過靜態代碼塊中判斷采用哪種形式的MethodHandler,在jdk1.8環境下,會使用Constructor 方式,后續對應調用的方法為getMethodHandleJava8(Method method),其他環境下采用Method方式,調用方法為getMethodHandleJava9(Method method)

    • 定義內部接口 MapperMethodInvoker,其唯一接口方法為:

      Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
      

      該接口有兩個私有實現類:PlainMethodInvoker,DefaultMethodInvoker

      • PlainMethodInvoker 類通過MyBatis自定義的MapperMethod來執行對應的sqlSession 請求

        //通過構造方法注入
        private final MapperMethod mapperMethod;
        
        @Override
            public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
              return mapperMethod.execute(sqlSession, args);
            }
        
      • DefaultMethodInvoker 類采用jdk自帶的MethodHandler方式,通過綁定代理類來調用sqlSession請求。

        //通過構造方法注入
        private final MethodHandle methodHandle;
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return methodHandle.bindTo(proxy).invokeWithArguments(args);
        }
        
    • MapperProxy 實現InvocationHandler接口中的invoke方法:

      • 首先判斷傳入的method聲明類型是否為Object.class,若是則直接調用method.invoke(this, args);
      • 否則調用MapperProxy私有方法 cachedInvoker(Object proxy, Method method, Object[] args)
    • cachedInvoker方法:

      • 首先將傳入method的加入到methodCache中(如果不存在時加入)。

      • 根據該方法是否是isDefault類型執行不同的邏輯。

        • 如果isDefault == true,則調用DefaultMethodInvoker(根據privateLookupInMethod是否為null來決定使用getMethodHandleJava8還是getMethodHandleJava9)

        • 如果isDefault == false,則調用PlainMethodInvoker,關于MapperMethod介紹,請繼續閱讀

          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
          

    MapperMethod

    MapperMethod類除了定義相關方法外,還定義了兩個內部類:SqlCommandMethodSignature

    SqlCommand

    該類定義兩個屬性:String類型的name、SqlCommandType 類型的type及對應的get方法。SqlCommandType 為枚舉類,其值為UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH。

    SqlCommand 還提供了一個有參構造方法,如下:

    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);
      /** 判斷ms是否為null
           *  1、如果不為null,則獲取對應的sql id 和執行類型并賦值給name、type
           *  2、如果為null,則再次判斷執行方法上是否有注解Flush,如果有則name設置為null,type設置為FLUSH;否則拋出BindingException
           */
      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);
        }
      }
    }
    

    該構造方法主要目的是根據configuration、mapperInterface、method參數獲取對應的name、type值。以用于MapperMethod中excute方法的邏輯處理。構造方法中調用了SqlCommand定義的私有方法,方法的具體邏輯見如下源碼注釋。

    /**
         * 1、根據接口全路徑名及方法名組裝成statementId
         * 2、判斷configuration 中是否存在該mappedStatement,若存在則直接返回
         * 3、如果不存在則從父類接口中繼續查找,如果找不到則返回null
         * 4、如果入參路徑就是方法所在的路徑,則直接返回null
         */
        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)) {
            return null;
          }
          for (Class<?> superInterface : mapperInterface.getInterfaces()) {
            if (declaringClass.isAssignableFrom(superInterface)) {
              MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                  declaringClass, configuration);
              if (ms != null) {
                return ms;
              }
            }
          }
          return null;
        }
    

    MethodSignature

    MethodSignature類定義了method相關屬性,具體內容參看如下源碼。

    public static class MethodSignature {
        /** 是否返回多值 */
        private final boolean returnsMany;
        /** 是否返回map */
        private final boolean returnsMap;
        /** 是否返回void類型 */
        private final boolean returnsVoid;
        /** 是否返回cursor */
        private final boolean returnsCursor;
        /** 是否返回optional */
        private final boolean returnsOptional;
        /** 返回類型 */
        private final Class<?> returnType;
        /** map主鍵 */
        private final String mapKey;
        /** 返回結果的handler索引 */
        private final Integer resultHandlerIndex;
        /** 返回rowBound索引 */
        private final Integer rowBoundsIndex;
        /** 參數名稱解析器 */
        private final 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.returnsOptional = Optional.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);
        }
    
       // boolean及get方法略
    
        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;
        }
    
        /** 判斷method的返回類型是否有注解主鍵,有則返回該主鍵value,沒有返回null */
        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;
        }
      }
    

    介紹完MapperMethod的兩個內部類,我們回過了頭來看下其自己的源碼結構。

    MapperMethod有兩個屬性 command及method,這兩個屬性是在MapperMethod構造方法中通過調用各自類型的構造方法進行初始化,源碼如下:

    private final SqlCommand command;
    private final MethodSignature method;
    
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
      this.command = new SqlCommand(config, mapperInterface, method);
      this.method = new MethodSignature(config, mapperInterface, method);
    }
    

    MapperMethod核心方法為execute,其邏輯如下:

    /** MapperMethod 核心執行邏輯根據command類型:
       *  insert、update、delete 分別調用對應的執行命令,同時調用rowCountResult 返回受影響的條數
       *  select操作,其執行會根據是否有結果處理器及返回數據類型調用不同的方法
       */
      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);
              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;
      }
    	
      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;
      }
    
      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));
        }
      }
    
      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.selectList(command.getName(), param, rowBounds);
        } else {
          result = sqlSession.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;
      }
    
      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.selectCursor(command.getName(), param, rowBounds);
        } else {
          result = sqlSession.selectCursor(command.getName(), param);
        }
        return result;
      }
    
      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;
      }
    
      @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);
        }
      }
    
      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.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
        } else {
          result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
        }
        return result;
      }
    

    如上就是MapperMethod類文件中主要的邏輯介紹。MapperMethod會在實例化PlainMethodInvoker時進行實例化。

    BindingException

    綁定異常處理類,在Mybatis的綁定處理過程中,若出現異常情況則會拋出該類型的異常。BindingException本質上還是繼承于RuntimeException類。

    public class BindingException extends PersistenceException {
    
      private static final long serialVersionUID = 4300802238789381562L;
    
      public BindingException() {
        super();
      }
    
      public BindingException(String message) {
        super(message);
      }
    
      public BindingException(String message, Throwable cause) {
        super(message, cause);
      }
    
      public BindingException(Throwable cause) {
        super(cause);
      }
    }
    

    總結

    雖然Binding模塊代碼不多,但在設計層面還是下足了功夫,比如在Mapper采用JDK動態代理模式,在Mapper注冊時采用工廠模式等。

    關于MyBatis的Binding模塊介紹至此告一段落。感謝垂閱,如有不妥之處請多多指教~


    微觀世界,達觀人生。

    做一名踏實的coder !

    歡迎掃描下方二維碼,關注我的個人微信公眾號 ~

    在這里插入圖片描述

    版權聲明:本文為iteye_7068原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/iteye_7068/article/details/107630570

    智能推薦

    MyBatis源碼解析-logging模塊

    簡述 本文主要說明MyBatis的logging模塊是如何適配各種日志組件的,以及為什么MyBatis日志組件的優先加載順序為sf4j -> commons logging -> log4j2 -> log4j -> jdk logging 適配器模式 MyBatis主要通過適配器模式完成各種日志組件的適配工作,適配器模式是23種設計模式的一種,這里只會做簡要的說明,具體去...

    MyBatis源碼解析 - 反射模塊

    MyBatis源碼解析 - 反射模塊 1. 前言 ? 該模塊位于org.apache.ibatis.reflection包中,MyBatis在進行參數處理、結果映射等操作時,會涉及大量的反射操作。Java 中的反射雖然功能強大,但是代碼編寫起來比較復雜且容易出錯,為了簡化反射操作的相關代碼,MyBatis提供了專門的反射模塊,它對常見的反射操作做了進一步封裝,提供了更加簡潔方便的反射API。本節就...

    MyBatis源碼解析 - 解析器模塊

    MyBatis源碼解析 - 解析器模塊 1. 前言 在MyBatis中涉及多個xml文件,解析這些xml文件自然離不開解析器。本文就來分析一下解析器模塊。 2. 準備工作 xml常見的解析方式分為以下三種: DOM ( Document Object Model)解析方式 SAX (Simple APIfor XML)解析方式 StAX( Streaming API for XML)解析方式 - ...

    mybatis-基礎源碼解析

    Mybatis解析mapper文件的方式(優先級package最高) XMLConfigBuilder.java package/resource/url/class 如何解析mapper文件 MappedStatement.java相當于mapper文件的java文件...

    MyBatis源碼解析 - 類型轉換模塊

    MyBatis源碼解析 - 類型轉換模塊 前言 JDBC數據類型與Java語言中的數據類型并不是完全對應的,所以在PreparedStatement為SQL語句綁定參數時,需要從Java類型轉換成JDBC類型,而從結果集中獲取數據時,則需要從JDBC類型轉換成Java類型。MyBatis 使用類型處理器完成上述兩種轉換,如圖所示。 在MyBatis中使用JdbeType這個枚舉類型代表JIDBC中...

    猜你喜歡

    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壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

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