Mybatis源碼學習第六課---核心層源碼分析--binding模塊

目錄

一.一個核心問題

二 、binding模塊分析

  2.1、binging 模塊核心組件關係如下圖:

  2.2、MapperRegistry解析

  2.3、MapperProxyFactory

  2.4 MapperProxy

  2.5 MapperMethod

2.5.1 SqlCommand

2.5.2  ParamNameResolver

2.5.3 MethodSignature

     2.5.4 MapperMethod.execute()

三 、數據庫執行流程分析

  3.1 流程分析

3.1.1 創建動態代理對象流程

3.1.2 執行查詢流程

四、總結

參考


一.一個核心問題

   爲什麼通過mapper接口就可以執行數據庫操作(mapper接口沒有實現類型)。

  Ans: 通過mapper接口動態代理的增強+ 配置文件解讀。

動態代理增強就是binding模塊的功能。
從表現來講,bingding的主要功能是將面向mapper接口編程轉換成session中對應的方法執行。

二 、binding模塊分析

  2.1、binging 模塊核心組件關係如下圖:

                          

  1.  MapperRegistry : mapper 接口和對應動態代理工廠 的註冊中心。
  2.  MapperProxyFactory :     用於生成mapper接口動態代理的實例對象;
  3.  MapperProxy:實現了InvocationHandler接口,它是增強mapper接口的實現;
  4.  MapperMethod:封裝了Mapper接口中對應方法的信息,以及對應的sql語句的信息;它是mapper接口與映射配置文件中sql語句的橋樑;

2.2、MapperRegistry解析

       MapperRegistry是 Mapper 接口及其對應的代理對象工廠的註冊中心。

      MapperRegistry中字段的含義和功能如下:

 private final Configuration config;//Configuration對象,mybatis全局唯一的
  //記錄了mapper接口與對應MapperProxyFactory之間的關係
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

        在Mybatis初始化過程中會讀取配置文件以及mapper接口中的註解信息,並調用MapperRegistry.addMapper()方法將mapper接口的工廠類添加到mapper註冊中心(MapperRegistry.knownMappers 集合 )該集合的 key 是 Mapper 接口對應的 Class 對象, value 爲 MapperProxyFactory 工廠對象,可以爲 Mapper 接口創建代理對象, MapperProxyFactory 的實現馬上就會分析到。 MapperRegistry.addMapper()方法的 部分實現如下:

 //將mapper接口的工廠類添加到mapper註冊中心
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {//監測是否爲接口
        if (hasMapper(type)) {//判斷是否已經初始化過了
          throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
      boolean loadCompleted = false;
      try {
    	//實例化Mapper接口的代理工程類,並將信息添加至knownMappers
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        //解析接口上的註解信息,並添加至configuration對象
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

          在需要執行某sql語句時候,會先調用 MapperRegistry.getMapper()方法獲得Mapper接口的代理對象,例如:

 TUserMapper mapper = sqlSession.getMapper(TUserMapper.class); mapper實際上是通過Jdk動態代理爲TUserMapper接口生產代理對象。其代碼如下所示:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //根據type獲取 其動態代理工廠類
    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);
    }
  }

2.3、MapperProxyFactory

    MapperProxyFactory 主要負責創建代理對象, 其中核心字段的含義和功能如下:

  //mapper接口的class對象
  private final Class<T> mapperInterface;
  //key是mapper接口中的某個方法的method對象,value是對應的MapperMethod,
  // MapperMethod對象不記錄任何狀態信息,所以它可以在多個代理對象之間共享
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

 MapperProxyFactory.newInstance(SqlSession sqlSession)方法實現了創建mapperInterface接口的代理對象的功能,其具體代碼如下:

 public T newInstance(SqlSession sqlSession) {
    //每次調用都會創建新的MapperProxy對象
    final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

protected T newInstance(MapperProxy<T> mapperProxy) {
    //創建實現了mapper接口的動態代理對象
    /**
     * jdk動態代理獲得代理對象
     * 參數一 被代理對象的類加載器
     * 參數二  被代理對象的接口
     * 參數三 代理對象
     */
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

2.4 MapperProxy

     MapperProxy 實現了 lnvocationHandler 接 口,在介紹 JDK 動態代理,該接口的實現是代理對象的核心邏輯,這裏不再重複描述。 MapperProxy 中核心宇段的含義和功能 如下:

 private final SqlSession sqlSession;//當前查詢的sqlSession對象
  private final Class<T> mapperInterface;// Mapper接口對應的class對象

  ////key是mapper接口中的某個方法的method對象,value是對應的MapperMethod,
  // MapperMethod對象不記錄任何狀態信息,所以它可以在多個代理對象之間共享
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

 MapperProxy. invoke()方法是代理對象執行的主要邏輯, 實現如下:

/**
   *
   * @param proxy 代理對象
   * @param method 被代理對象中方法
   * @param args   方法中餓入參
   * @return 代理對象
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //如果目標方法繼承自 Object ,則直接調用目標方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {// ..針對 Java7 以上版本對動態類型語言的支持
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //methodCache 緩存中獲取MapperMethod 對象,如採緩存中沒有,則創建新的 MapperMethod 對象並添加到緩存中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //調用 MapperMethod.execute()方法執行 SQL 語句
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method,
        k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

2.5 MapperMethod

    MapperMethod 中封裝了Mapper接口中對應方法的信息,以及對應SQL語句的信息。我們可以把MapperMethod的看着連接Mapper接口以及映射配置文件中定義的SQL語句的橋樑。

    MapperMethod中各個字段的信息如下:

private final SqlCommand command;//從configuration中獲取方法的命名空間.方法名以及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);
  }

2.5.1 SqlCommand

 SqlCommand 是MapperMethod中定義的內部類,他使用name字段記錄SQL語句的名稱,使用 type 宇段( SqlCommandType 類型)記錄了 SQL 語句的類型。 SqlCommandType 是枚舉類 型,有效取值爲 UNKNOWN、 INSERT、 UPDATE、 DELETE、 SELECT、 FLUSH。

 public static class SqlCommand {
    //sql的名稱,命名空間+方法名稱
    private final String name;
    //獲取sql語句類型
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();//獲取名稱
      final Class<?> declaringClass = method.getDeclaringClass();
      //從configuration中獲取mappedStatement
      // 用於存儲mapper.xml文件中的select、insert、update和delete節點,同時還包
      //含了這些節點的很多重要屬性;
      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 {//如果如果mappedStatement不爲空
        name = ms.getId();//獲取sql的名稱,命名空間+方法名稱
        type = ms.getSqlCommandType();//獲取sql語句的類型
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

    public String getName() {
      return name;
    }

    public SqlCommandType getType() {
      return type;
    }
    //從configuration中獲取mappedStatement
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      String statementId = mapperInterface.getName() + "." + methodName;//sql語句的id爲命名空間+方法名字
      if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);//從configuration中獲取mappedStatement
      } 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;
    }
  }

2.5.2  ParamNameResolver

  ParamNameResolver(Resolver [riˈzɑlvər] 解析器; ) 解析mapper接口方法中的入參;

     在 MethodSignature 中, 會使用 ParamNameResolver 處理 Mapper 接口中定義的方法的參數 列表。 ParamNameResolver 使用 name 宇段( SortedMap<Integer, String>類型)記錄了參數在參 數列表中的位置索引與參數名稱之間的對應關係,其中 key 表示參數在參數列表中的索引位置, value 表示參數名稱,參數名稱可以通過@Param 註解指定,如果沒有指定@Param 註解,則使 用參數索引作爲其名稱。 

2.5.3 MethodSignature

MethodSignature(Signature [ˈsɪɡnətʃər] 明顯特徵;)封裝mapper接口方法的相關信息(入參,返回類型);

MethodSignature 中核心內容如下:

   private final boolean returnsMany;//返回參數是否爲集合類型或數組
    private final boolean returnsMap;//返回參數是否爲map
    private final boolean returnsVoid;//返回值爲空
    private final boolean returnsCursor;//返回值是否爲遊標類型
    private final boolean returnsOptional;//返回值是否爲Optional
    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.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);
    }
    // 負責將 args[]數紐( 用戶傳入的實參列表)轉換成 SQL 語句對應的參數列表,它是通過上面介紹的
    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }

     2.5.4 MapperMethod.execute()

   介紹完 MapperMethod 中定義的內部類, 回到 MapperMethod 繼續分析。 MapperMethod 中 最核心的方法是 execute()方法,它會根據 SQL 語句的類型調用 SqISession 對應的方法完成數據 庫操作。 SqlSession 是 MyBatis 的核心組件之一,其具體實現後面會詳細介紹,這裏讀者暫時只 需知道它負責完成數據庫操作即可。

      MapperMethod.execute()方法的具體實現如下:  

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //根據 SQL 語句的類型調用 SqlSession 對應的方法
    switch (command.getType()) {
      case INSERT: {
        //使用 ParamNameResolver 處理 args[]數組(用戶傳入的實參列表),將用戶傳入的 實參與
        // 指定參數名稱關聯起
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
      
      }
      case DELETE: {
   
      }
      case SELECT:
        //處理返回值爲 void 且 ResultSet 通過 ResultHandler 處理的方法
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {///處理返回值爲集合或數組的方法
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {//處理返回值爲 Map 的方法
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {//處理返回值爲 Cursor 的方法
          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;
  }

三 、數據庫執行流程分析

  3.1 流程分析

 @Test
    public void testManyParamQuery(){
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 1.讀取mybatis配置文件創SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2.獲取sqlSession 方法級別的
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //// 3.獲取對應mapper
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
        String email = "qq.com";
        Byte sex = 1;
        // 第三種方式用對象
        EmailSexBean esb = new EmailSexBean();
        esb.setEmail(email);
        esb.setSex(sex);
        List<TUser> list3 = mapper.selectByEmailAndSex3(esb);
        System.out.println("javabean:"+list3.size());
    }

3.1.1 創建動態代理對象流程

 //// 3.獲取對應mapper
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

step1.SqlSession的唯一實現是DefaultSqlSession,getMapper方法如下:

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

step2.調用Configuration的getMapper方法:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
/*mapper接口的動態代理註冊中心*/
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

public MapperRegistry(Configuration config) {
    this.config = config;
  }

step3.調用MapperRegistry的getMapper方法:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //根據type獲取 其動態代理工廠類
    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);
    }
  }

step4.調用MapperProxyFactory的newInstance:

  public T newInstance(SqlSession sqlSession) {
    //每次調用都會創建新的MapperProxy對象
    final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

 protected T newInstance(MapperProxy<T> mapperProxy) {
    //創建實現了mapper接口的動態代理對象
    /**
     * jdk動態代理獲得代理對象
     * 參數一 被代理對象的類加載器
     * 參數二  被代理對象的接口
     * 參數三 代理對象
     */
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

 

最後DEBUG可以看出來得到的mapper實際上是 mapper接口class對象的動態代理對象

3.1.2 執行查詢流程

List<TUser> list3 = mapper.selectByEmailAndSex3(esb);

step1.動態代理最終要調用的InvocationHandler——mapperProxy

/**
   *
   * @param proxy 代理對象
   * @param method 被代理對象中方法
   * @param args   方法中餓入參
   * @return 代理對象
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //如果目標方法繼承自 Object ,則直接調用目標方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {// ..針對 Java7 以上版本對動態類型語言的支持
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //methodCache 緩存中獲取MapperMethod 對象,如採緩存中沒有,則創建新的 MapperMethod 對象並添加到緩存中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //調用 MapperMethod.execute()方法執行 SQL 語句
    return mapperMethod.execute(sqlSession, args);
  }
 

step2.獲取MapperMethod對象

 private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

MapperMethod封裝了Mapper接口中對應方法的信息,以及對應的sql語句的信息,它是mapper接口與映射配置文件中sql語句的橋樑,MapperMethod對象不記錄任何狀態信息,所以它可以在多個代理對象之間共享。

  •  SqlCommand:從configuration中獲取方法的命名空間、方法名以及SQL語句的類型;
  • MethodSignature:封裝mapper接口方法的相關信息(入參、返回類型);
  • ParamNameResolver:解析mapper接口方法中的入參。

step2-1.獲取SqlCommand

  

step2-2.獲取MethodSignature

  

step3.調用MapperMethod.execute執行sql

return mapperMethod.execute(sqlSession, args);

四、總結

 

step1.通過動態代理創建了代理對象MapperProxy
step2.MapperProxy最後調用的是MapperMethod的方法執行
最後的核心落在了MapperMethod,其封裝了Mapper接口中對應方法的信息,以及對應的sql語句的信息,它是mapper接口與映射配置文件中sql語句的橋樑。

參考

  • 1)享學課堂Lison老師筆記

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章