前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >带你彻底搞懂MyBatis的底层实现之binding模块

带你彻底搞懂MyBatis的底层实现之binding模块

作者头像
用户4919348
发布2021-06-01 21:11:37
5310
发布2021-06-01 21:11:37
举报
文章被收录于专栏:波波烤鸭波波烤鸭

  基础支持层位于MyBatis整体架构的最底层,支撑着MyBatis的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块。不仅仅为MyBatis提供基础支撑,也可以在合适的场景中直接复用。

在这里插入图片描述
在这里插入图片描述

  上篇文章我们给大家聊了下日志模块,本篇文章我们重点来聊下binding模块。

binding模块

   接下来我们看看在org.apache.ibatis.binding包下给我们提供的Binding模块,这个模块在我们前面使用的

代码语言:javascript
复制
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

  这种方式中发挥了很重要的作用,我们先来看这个包中给我们提供的工具类

在这里插入图片描述
在这里插入图片描述

  大家会发现这个包里面提供的工具比较少,就几个,我们先来分别了解下他们的作用,然后在串联起来。

1 MapperRegistry

  通过名称我们可以看出这显然是一个注册中心,这个注册中是用来保存MapperProxyFactory对象的,所以这个注册器中提供的功能肯定是围绕MapperProxyFactory的添加和获取操作,我们来看看具体的代码逻辑

成员变量

代码语言:javascript
复制
  private final Configuration config;
  // 记录 Mapper 接口和 MapperProxyFactory 之间的关系
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

addMapper方法

代码语言:javascript
复制
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) { // 检测 type 是否为接口
      if (hasMapper(type)) { // 检测是否已经加装过该接口
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.

        // 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

getMapper方法

代码语言:javascript
复制
  /**
   * 获取Mapper接口对应的代理对象
   */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取Mapper接口对应的 MapperProxyFactory 对象
    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);
    }
  }

通过这个方法本质上获取的就是Mapper接口的代理对象。

2 MapperProxyFactory

  MapperProxyFactory是一个工厂对象,专门负责创建MapperProxy对象。其中核心字段的含义和功能如下:

代码语言:javascript
复制
 /**
   * MapperProxyFactory 可以创建 mapperInterface 接口的代理对象
   *     创建的代理对象要实现的接口
   */
  private final Class<T> mapperInterface;
  // 缓存
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

// ....

  /**
   * 创建实现了 mapperInterface 接口的代理对象
   */
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

3 MapperProxy

  通过MapperProxyFactory创建的MapperProxy是Mapper接口的代理对象,实现了InvocationHandler接口,通过前面讲解的动态代理模式,那么这部分的内容就很简单了。

代码语言:javascript
复制
  private final SqlSession sqlSession; // 记录关联的 SqlSession对象
  private final Class<T> mapperInterface; // Mapper接口对应的Class对象
  // 用于缓存MapperMethod对象,key是Mapper接口方法对应的Method对象,value是对应的MapperMethod对象。‘
  // MapperMethod对象会完成参数转换以及SQL语句的执行
  // 注意:MapperMethod中并不会记录任何状态信息,可以在多线程间共享
  private final Map<Method, MapperMethodInvoker> methodCache; 

  MapperProxy.invoke() 方法是代理对象执行的主要逻辑,实现如下:

代码语言:javascript
复制
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,无需走到执行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
        // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  然后在cachedInvoker中主要负责维护methodCache这个缓存集合

代码语言:javascript
复制
  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // Java8 中 Map 的方法,根据 key 获取值,如果值是 null,则把后面Object 的值赋给 key
      // 如果获取不到,就创建
      // 获取的是 MapperMethodInvoker(接口) 对象,只有一个invoke方法
      // 根据method 去methodCache中获取 如果返回空 则用第二个参数填充
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          // 接口的默认方法(Java8),只要实现接口都会继承接口的默认方法,例如 List.sort()
          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 {
          // 创建了一个 MapperMethod
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

4 MapperMethod

  MapperMethod中封装了Mapper接口中对应方法的信息,以及SQL语句的信息,我们可以把MapperMethod看成是配置文件中定义的SQL语句和Mapper接口的桥梁。

在这里插入图片描述
在这里插入图片描述

属性和构造方法

代码语言:javascript
复制
  // statement id (例如:com.gupaoedu.mapper.BlogMapper.selectBlogById) 和 SQL 类型
  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);
  }

4.1 SqlCommand

  SqlCommand是MapperMethod中定义的内部类,记录了SQL语句名称以及对应的类型(UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH)

代码语言:javascript
复制
  public static class SqlCommand {

    private final String name; // SQL语句的的名称
    private final SqlCommandType type; // SQL 语句的类型

    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;
    }

    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      // statementId = Mapper接口全路径 + 方法名称 比如:com.gupaoedu.mapper.UserMapper
      String statementId = mapperInterface.getName() + "." + methodName;
      if (configuration.hasStatement(statementId)) {// 检查是否有该名称的SQL语句
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      // 如果Mapper接口还有父类 就递归处理
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }

4.2 MethodSignature

  MethodSignature也是MapperMethod的内部类,在其中封装了Mapper接口中定义的方法相关信息。

代码语言:javascript
复制
    private final boolean returnsMany; // 判断返回是否为 Collection类型或者数组类型
    private final boolean returnsMap; // 返回值是否为 Map类型
    private final boolean returnsVoid; // 返回值类型是否为 void
    private final boolean returnsCursor; // 返回值类型是否为 Cursor 类型
    private final boolean returnsOptional; // 返回值类型是否为 Optional 类型
    private final Class<?> returnType; // 返回值类型
    private final String mapKey; // 如果返回值类型为 Map  则 mapKey 记录了作为 key的 列名
    private final Integer resultHandlerIndex; // 用来标记该方法参数列表中 ResultHandler 类型参数的位置
    private final Integer rowBoundsIndex; // 用来标记该方法参数列表中 rowBounds 类型参数的位置
    private final ParamNameResolver paramNameResolver; // 该方法对应的 ParamNameResolver 对象

构造方法中完成了相关信息分初始化操作

代码语言:javascript
复制
    /**
     * 方法签名
     * @param configuration
     * @param mapperInterface
     * @param method
     */
    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;
      // getUniqueParamIndex 查找指定类型的参数在 参数列表中的位置
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

getUniqueParamIndex的主要作用是 查找指定类型的参数在参数列表中的位置

代码语言:javascript
复制
    /**
     * 查找指定类型的参数在参数列表中的位置
     * @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 {
            // RowBounds 和 ResultHandler 类型的参数只能有一个,不能重复出现
            throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
          }
        }
      }
      return index;
    }

4.3 execute方法

   最后我们需要来看下再MapperMethod中最核心的方法execute方法,这个方法完成了数据库操作

代码语言:javascript
复制
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法
      case INSERT: {
        // 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来
        Object param = method.convertArgsToSqlCommandParam(args);
        // sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法
        // rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换
        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()) {
          // 返回值为空 且 ResultSet通过 ResultHandler处理的方法
          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);
          // 普通 select 语句的执行入口 >>
          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;
  }

在这个方法中所对应一些分支方法都还是比较简单的,可自行查阅。

4.4 核心流程串联

  解析来我们看看在系统具体操作过程中解析器模块在哪些地方发挥了作用

首先在映射文件加载解析的位置。

代码语言:javascript
复制
  public void parse() {
    // 总体上做了两件事情,对于语句的注册和接口的注册
    if (!configuration.isResourceLoaded(resource)) {
      // 1、具体增删改查标签的解析。
      // 一个标签一个MappedStatement。 >>
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。
      // 一个namespace 一个 MapperProxyFactory >>
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

  在bindMapperForNamespace中会完成Mapper接口的注册并调用前面介绍过的addMapper方法

  然后就是在我们执行

代码语言:javascript
复制
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectUserList();

这两行代码的内部逻辑,首先看下getMapper方法

代码语言:javascript
复制
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

继续

代码语言:javascript
复制
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // mapperRegistry中注册的有Mapper的相关信息 在解析映射文件时 调用过addMapper方法
    return mapperRegistry.getMapper(type, sqlSession);
  }

然后就是从MapperRegistry中获取对应的MapperProxyFactory对象。

代码语言:javascript
复制
  /**
   * 获取Mapper接口对应的代理对象
   */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取Mapper接口对应的 MapperProxyFactory 对象
    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);
    }
  }

然后根据MapperProxyFactory对象获取Mapper接口对应的代理对象。

代码语言:javascript
复制
  /**
   * 创建实现了 mapperInterface 接口的代理对象
   */
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

上面简单的时序图为

在这里插入图片描述
在这里插入图片描述

然后我们再来看下调用代理对象中的方法执行的顺序

代码语言:javascript
复制
List<User> list = mapper.selectUserList();

会进入MapperProxy的Invoker方法中

代码语言:javascript
复制
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,无需走到执行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
        // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

然后进入PlainMethodInvoker中的invoke方法

代码语言:javascript
复制
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // SQL执行的真正起点
      return mapperMethod.execute(sqlSession, args);
    }

然后会进入到 MapperMethod的execute方法中

代码语言:javascript
复制
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法
      case INSERT: {
        // 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来
        Object param = method.convertArgsToSqlCommandParam(args);
        // sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法
        // rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换
        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()) {
          // 返回值为空 且 ResultSet通过 ResultHandler处理的方法
          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);
          // 普通 select 语句的执行入口 >>
          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;
  }

之后就会根据对应的SQL类型而调用SqlSession中对应的方法来执行操作

~~ 好了,binding模块的内容就给大家介绍到这里,如果对你有帮助,欢迎点赞关注加收藏 下篇我们介绍 MyBatis中的缓存模块

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-05-27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • binding模块
    • 1 MapperRegistry
      • 2 MapperProxyFactory
        • 3 MapperProxy
          • 4 MapperMethod
            • 4.1 SqlCommand
            • 4.2 MethodSignature
            • 4.3 execute方法
            • 4.4 核心流程串联
        相关产品与服务
        微服务引擎 TSE
        微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档