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老师笔记

 

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