SQL执行流程(一)——创建Mapper接口代理对象

247 阅读4分钟

简介

使用MyBatis进行数据库操作时,需要先调用SqlSession接口的getMapper方法为Mapper接口生成实现类。然后就可以通过Mapper接口进行数据库操作。

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

为Mapper接口创建代理对象

SqlSession是通过JDK动态代理的方式为接口生成代理对象的。在调用接口方法时,相关调用会被代理逻辑拦截。在代理逻辑中可根据方法名及接口获取到当前方法对应的SQL以及其他一些信息,拿到这些信息即可进行数据库操作。

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

//..省略中间调用...

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // 从 knownMappers 中获取与 type 对应的 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);
  }
}

knownMappers 中的 MapperProxyFactory 对象是在解析配置文件中的 <mappers> 标签所创建的。在获取到 MapperProxyFactory 对象后,即可调用方法 newInstance() 为Mapper接口生成代理对象。

//MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
  // 创建 MapperProxy 对象,MapperProxy 实现了 InvocationHandler接口
  // 代理逻辑封装在此类中
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  //调用重载方法创建代理对象
  return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
  // 通过JDK动态代理创建代理对象
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

执行代理逻辑

MapperProxy 类实现了 InvocationHandler 接口,通过动态代理拦截了 Mapper 接口方法去执行代理逻辑。

// MapperProxy
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 {
      // 创建MapperMethodInvoker接口的对象,用于执行真正的代理逻辑
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

创建 MapperMethodInvoker

MapperMethodInvoker 是个接口,里面只有1个 invoke() 方法,用于执行不同的代理逻辑。该接口有2个实现类,分别是 DefaultMethodInvokerPlainMethodInvoker。实例化过程在 cachedInvoker() 方法中。

// MapperProxy
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
  try {
    MapperMethodInvoker invoker = methodCache.get(method);
    if (invoker != null) {
      return invoker;
    }

    return methodCache.computeIfAbsent(method, m -> {
      // 支持JDK 1.8特性,在接口中可以实现default方法。
      if (m.isDefault()) {
        try {
          // JDK1.8和JDK1.9 执行default方法不同的实现逻辑
          if (privateLookupInMethod == null) {
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          } else {
            return new DefaultMethodInvoker(getMethodHandleJava9(method));
          }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
          throw new RuntimeException(e);
        }
        
      // 执行定义在Mapper接口中方法的代理逻辑   
      } else {
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });
  } catch (RuntimeException re) {
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
  }
}

定义Mapper接口中的方法的代理逻辑是在 PlainMethodInvoker 类,它的 invoke() 方法实际是将职责委托给MapperMethod类的的 execute() 方法。所以这里就能确定,将Mapper接口中的方法和XML映射文件所关联起来,就是 MapperMehtod 类的职责

创建MapperMethod

在创建 PlainMethodInvoker 类的时候,会实例化一个 MapperMethod

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // 创建SqlCommand对象,包含一些SQL信息
    this.command = new SqlCommand(config, mapperInterface, method);
    // 创建MethodSignature对象,顾名思义,包含一些方法签名的信息
    this.method = new MethodSignature(config, mapperInterface, method);
  }

创建SqlCommand

SqlCommand 对象保存了与该方法所关联的SQL语句以及它的类型。

public static class SqlCommand {

  private final String name;
  private final SqlCommandType type;

  public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    
    // 解析MappedStatement
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
        configuration);
    
    // 判断当前方法是否有对应的MappedStatement    
    if (ms == null) {
      //检测当前方法是否有@Flush注解
      if (method.getAnnotation(Flush.class) != null) {
        name = null;
        type = SqlCommandType.FLUSH;
      } else {
        // 很常见的异常,没查询到与方法关联的SQL语句
        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);
      }
    }
  }
}

创建MethodSignature

MethodSignature 即方法签名,该类保存了一些和目标方法相关的信息。比如方法的返回类型、参数列表等。

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();
  }
  
  // 检测返回值类型是否是void、集合或数组、Cursor、Optional 等
  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);
  
  // 解析 @MapKey 注解,并获取注解的内容
  this.mapKey = getMapKey(method);
  this.returnsMap = this.mapKey != null;
  
  // 获取 RowBounds 参数在参数列表中的位置,如果参数列表中包含多个 RowBounds 参数,则方法会抛出异常
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  // 获取ResultHandler参数在参数列表中的位置
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
  // 解析参数列表
  this.paramNameResolver = new ParamNameResolver(configuration, method);
}

解析方法参数

ParamNameResolver 用于解析方法参数,可以得到参数下标与参数名的映射关系。这些映射关系存储在 names 成员变量中。

// ParamNameResolver
public ParamNameResolver(Configuration config, Method method) {
  this.useActualParamName = config.isUseActualParamName();
  
  // 获取参数类型列表
  final Class<?>[] paramTypes = method.getParameterTypes();
  // 获取参数注解
  final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  final SortedMap<Integer, String> map = new TreeMap<>();
  int paramCount = paramAnnotations.length;
  
  for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    // 检测当前的参数类型是否为 RowBounds 或 ResultHandler
    if (isSpecialParameter(paramTypes[paramIndex])) {
      continue;
    }
    
    String name = null;
    //for循环遍历是否含有@Param注解,如果有则使用@Param注解的值
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      if (annotation instanceof Param) {
        hasParamAnnotation = true;
        name = ((Param) annotation).value();
        break;
      }
    }
    
    // 如果不包含@Param注解
    if (name == null) {
      // 检测是否设置了 useActualParamName 全局配置
      if (useActualParamName) {
        // 获取指定索引位置的方法的参数名称
        // 这个方法需要jdk 1.8的支持
        name = getActualParamName(method, paramIndex);
      }
      if (name == null) {
        // 使用map.size()返回值作为参数名称。
        // 使用map.size()而不使用String.valueOf(paramIndex)是因为参数列表如果包含Row Bounds 或 ResultHandler,这2个参数会被忽略掉,导致名称不连续。
        name = String.valueOf(map.size());
      }
    }
    //paramIndex 到 name的映射
    map.put(paramIndex, name);
  }
  names = Collections.unmodifiableSortedMap(map);
}

执行 execute 方法

调用 MapperMethod#execute() 执行sql,最终的执行操作还是由 SqlSession 对象完成。

// MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  
  // 根据 SQL 类型执行相应的数据库操作
  switch (command.getType()) {
    case INSERT: {
      // 转换传入的方法的参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 执行insert操作,rowCountResult 方法用于处理返回值
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      // 转换传入的方法的参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 执行更新操作, rowCountResult 方法用于处理返回值
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      // 转换传入的方法的参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 执行删除操作, rowCountResult 方法用于处理返回值
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
       // 根据目标方法的返回类型进行相应的查询操作
      if (method.returnsVoid() && method.hasResultHandler()) {
        // 如果方法返回值为void,但参数列表中包含ResultHandler,表明使用者想通过ResultHandler的方式获取查询结果,而非通过返回值获取。
        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());
  }
  
  // 如果目标方法的返回类型为基本类型且返回值为null,抛出异常
  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参数

通过 MapperMethod#convertArgsToSqlCommandParam() 方法将方法参数转换为SQL执行所需要的参数。实际上 convertArgsToSqlCommandParam() 方法是将职责委托给ParamNameResolver#getNamedParams() 方法

// MapperMethod
public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}

// ParamNameResolver
public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {
     // 如果方法参数列表无@Param注解,且仅有一个非RowBounds和ResultHandler的参数,则返回该参数的值。names={0 : "name"}
    Object value = args[names.firstKey()];
    // 如果参数值是集合或者数组,则以参数名为key,参数值为value,存储在Map中并返回。
    return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
  } else {
    final Map<String, Object> param = new ParamMap<>();
    int i = 0;
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      // 添加<参数名、参数值>键值对到param 中
      param.put(entry.getValue(), args[entry.getKey()]);
     
      // 创建通用参数名称:param1,param2,param3......
      final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
      // 判断 names 中是否包含 genericParamName。比如用@Param("param1")显示配置参数名
      if (!names.containsValue(genericParamName)) {
        // 添加 <param*,value> 到 param中
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}