MyBatis 入门系列【20】 源码分析之 SQL 执行流程(1)

96 阅读7分钟

1. 前言

在通过代理模式获取到mapper接口的代理对象后,就可以直接使用代理对象调用mappper方法进行增删改查操作了。

接下来我们分析MyBatis整个Mapper中的SQL执行流程。

 List<User> dynamicUserList = userMapper.selectDynamicUserList(userQuery);

详细的执行流程如下图所示:

image.png

2. 源码分析

2.1 进入代理对象执行方法

代理对象执行时,会进入MapperProxyinvoke方法。

  /**
   *  执行代理 代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
   *
   * @param proxy 代理对象
   * @param method 执行方法
   * @param args 方法参数
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 代理执行
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

invoke方法判断不是Object中的方法后,进入cachedInvoker(method)方法对代理方法method进行处理,返回一个MapperMethodInvoker对象(可以理解为真实的执行方法对象)。因为当前methodJDK中的类,无法进行进行数据库复杂操作,需要进行进一步处理。

  /**
   * @param method 方法对象
   * @return MapperMethodInvoker
   * @throws Throwable
   */
  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method,
        // 处理方法
        m -> {
          // 如果是接口中的default修饰的方法
          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 {
            // 非default方法返回一个PlainMethodInvoker
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
          }
        });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

2.2 Method 转为 MapperMethod

在上一步cachedInvoker方法中返回MapperMethodInvoker 时,首先创建了一个MapperMethod对象。MapperMethod是对Mapper接口中的方法结合当前sqlSession中的Configuration对象,进一步处理为Mybatis中能执行操作的方法对象。

// 非default方法返回一个PlainMethodInvoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

MapperMethod的有参构造,创建了SqlCommandMethodSignature两个对象并赋值给MapperMethod的成员变量。

  /***
   * 构造方法
   * @param mapperInterface mapper接口
   * @param method 执行方法对象
   * @param config Configuration对象
   */
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

2.3 创建 SqlCommand 对象

SqlCommand对象有两个重要的属性nametypename存放MappedStatementIDtype存放当前的增删改查类型。

  public static class SqlCommand {

    private final String name; // org.pearl.mybatis.demo.dao.UserMapper.selectDynamicUserList
    private final SqlCommandType type; // UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH

    /**
     * SqlCommand 对象
     */
    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 方法名 selectDynamicUserList
      final String methodName = method.getName();
      // 接口 interface org.pearl.mybatis.demo.dao.UserMapper
      final Class<?> declaringClass = method.getDeclaringClass();
      // 获取MappedStatement
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
        configuration);
      // MappedStatement为null
      if (ms == null) {
        // 判断方法上是否有Flush注解
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
            + mapperInterface.getName() + "." + methodName);
        }
      } else {
        // 设置当前SqlCommand 的name 和  type属性
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

2.4 获取 MappedStatement

通过之前构建SqlSessionFactory源码分析中,我们了解到每个方法对应的MappedStatement都存放到了Configuration对象中名为mappedStatementsMap中,每个键值对存放了当前方法的ID及方法其他属性。

SqlCommand对象中的name就对应MappedStatementID

image.png

MapperMethod类中的resolveMappedStatement会从mappedStatements中获取改执行方法对应的MappedStatement

    /**
     * 获取MappedStatement
     */
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
                                                   Class<?> declaringClass, Configuration configuration) {
      // ID  org.pearl.mybatis.demo.dao.UserMapper.selectDynamicUserList
      String statementId = mapperInterface.getName() + "." + methodName;
      // 判断configuration 是否有该MappedStatement
      if (configuration.hasStatement(statementId)) {
        // 有直接获取并返回
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      // 没有MappedStatement 递归创建
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
            declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }

MapperMethod的构造方法中,通过2.3、2.4两个步骤,就创建了SqlCommand对象,此对象主要记录了执行方法的MappedStatementID及操作类型。 在这里插入图片描述

2.5 创建 MethodSignature对 象

MapperMethod的构造方法,接下来会创建MethodSignature对象。

MethodSignature方法签名类,主要提供存放当前方法的返回值类型,处理参数等功能。

  /**
   * 方法签名,静态内部类
   */
  public static class MethodSignature {

    // 是否多值查询
    private final boolean returnsMany;
    // 是否map查询
    private final boolean returnsMap;
    // 是否void查询
    private final boolean returnsVoid;
    // 是否游标查询
    private final boolean returnsCursor;
    // 是否Optional
    private final boolean returnsOptional;
    // 返回类型
    private final Class<?> returnType;
    // 获取mapKey的值
    private final String mapKey;
    // ResultHandler类型在method参数的序号
    private final Integer resultHandlerIndex;
    // 分页参数在method参数的序号
    private final Integer rowBoundsIndex;
    // 参数名称解析器
    private final ParamNameResolver paramNameResolver;

    public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 解析返回的类型 interface java.util.List
      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;
      // Mybatis默认的分页是通过RowBounds参数来实现的,并且是在内存里面进行的
      // 如果方法参数中包含RowBounds类型或其子类型的参数,找出这个参数在方法参数列表中的下标值
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      // 如果方法参数中包含ResultHandler类型或其子类型的参数,找出这个参数在方法参数列表中的下标值
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 参数名称解析器
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

2.6 MethodSignature 参数名称解析器

MethodSignature创建了一个ParamNameResolver参数名解析器。主要用来处理接口形式的参数,最后会把参数处放在一个map中。

在配置文件中有一个useActualParamName配置,参数名称解析器有用到,这个配置的意思是是否使用真实的参数名,比如(User user), 就会获取到user这个名称。但是javac编译后后,会优化,再通过反射获取,其名称就不是user了。

<setting name="useActualParamName" value="true" />

可以看到实际运行时,获取到的参数名是arg0

image.png ParamNameResolver成员属性:

  // Param 注解前缀
  public static final String GENERIC_NAME_PREFIX = "param";
  // 是否使用实际传入的参数名
  private final boolean useActualParamName;
  // 存放参数的位置和对应的参数名
  private final SortedMap<Integer, String> names;
  // 是否使用@param注解
  private boolean hasParamAnnotation;

ParamNameResolver构造方法解析参数逻辑:

  /**
   * 参数名解析器
   * 主要用来处理接口形式的参数,最后会把参数处放在一个map中
   *
   * @param config Configuration
   * @param method Method
   */
  public ParamNameResolver(Configuration config, Method method) {
    // 获取Configuration中的useActualParamName属性 true
    this.useActualParamName = config.isUseActualParamName();
    // 参数的类型数组
    final Class<?>[] paramTypes = method.getParameterTypes();
    // 方法注解的二维数组,每一个方法的参数包含一个注解数组。
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    // 创建一个有序的Map
    final SortedMap<Integer, String> map = new TreeMap<>();
    // 方法注解的集合的长度
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    // 循环注解,没有注解时,paramCount为1
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      // 如果为特殊参数(RowBounds(分页对象) 和 ResultHandler(结果处理)),则不会记入mapper的实际参数
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      //判断参数是否由@Param注解修饰,如果有Param注解 hasParamAnnotation = true,参数的名称为Param定义的值
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      // 没有Param注解
      if (name == null) {
        // @Param was not specified.
        //如果配置了useActualParamName=true的话,则取实际参数的名称 arg0
        if (useActualParamName) {
          //
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          //否则,该参数的名称为0,1....n
          name = String.valueOf(map.size());
        }
      }
      // 将参数序列号  参数名放入有序Map中 { 0 ,arg0}
      map.put(paramIndex, name);
    }
    // 将Map 赋值给names
    names = Collections.unmodifiableSortedMap(map);
  }

构建完参数解析器后,MethodSignature方法签名对象就构建完成了:

image.png

2.7 构建 PlainMethodInvoker 对象

SqlCommandMethodSignature对象构建完成后,MapperMethod对象也就完成了初始化,第一步中的cachedInvoker(method),就只有就有一步了,构建PlainMethodInvoker对象。

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

通过对mapper方法的重重处理,最后获取PlainMethodInvoker对象。构造方法很简单,就是把处理后的MapperMethod方赋值给成员属性。

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

2.8 第二个 invoke

PlainMethodInvoker创建成功后,拥有了MapperMethod对象,最终回到第一步代码中,调用PlainMethodInvokerinvoke方法,开始执行数据库操作。

        // 代理执行
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);