基础支持层——binding模块

2019-10-30 书籍 《MyBatis技术内幕》

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

整体架构

这篇文章介绍MyBatis的binding模块

iBatis (MyBatis 的前身)中,查 询 一个 Blog 对 象时 需要调 用 SqlSession.queryForObject ("selectBlog", blogld)方法。

其中,SqlSession.queryForObject()方法会 执 行指定的 SQL 语 句进 行 查 询 并 返回一个 结 果对 象,第一个 参 数 selectBlog指明了具体 执 行的SQL语 句的id,该 SQL 语 句定义 在相应 的映射配置文件中。

如果我们 错 将 selectBlog写 成了 selectBlogl,在初始 化过 程中,MyBatis是无法提示该 错 误 的,而在实 际 调 用queryForObject(selectBlog1,blogld) 方法时才会抛出异常,开发人员才能知道该错误。

MyBatis提供了 binding模块 用于解决 上述问 题 ,我们 可以定义 一个 Mapper接口,该 示例中为 BlogMapper接口,具体 代码 如下所示。

这 里的 BlogMapper接口并 不需要继 承任何其他接口,而且开 发 人员 不需要提供该 接口的实 现 。

public interface BlogMapper {
  // 在映射文件中存在一个<select>接口,id为 selectById
  public Blog selectById(int id);
}

Mapper接口中定义 了 SQL语 句对 应 的方法,这 些方法在MyBatis初始化过 程中会 与 映 射配置文件中定义 的SQL语 句相关 联 。如果存在无法关 联 的SQL语 句,在 MyBatis的初始化 节 点就会 抛出异 常。

我们可以调 用Mapper接口中的方法执 行相应 的SQL语 句,这样编译器就可以帮助我们提早发现上述问题 。

查询blog:

blogMapper mapp = session.getMapper(BlogMapper.class);
Blog blog = mapp.selectById(1);

binding模块核心组件

image-20200610142911476

MapperRegistry&MapperProxyFactory

MapperRegistry

MapperRegistryMapper接口及其对 应 的代理对 象工厂 的注册 中心。ConfigurationMyBatis全局性的配置对 象,在 MyBatis初始化的过 程中,所有配置信息会 被解析成相应 的对 象并 记 录 到Configuration对 象中。这 里 关 注 Configuration.mapperRegistry字 段 , 它 记 录 当 前 使 用 的 MapperRegistry对 象 。

MapperRegistry中字段及含义

// Configuration对象,MyBatis中全局唯一的配置对象,其中包含了所有配置信息
private final Configuration config;

// 记录Mapper接口与对应MapperRegistry之间的关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers 
  = new HashMap<Class<?>, MapperProxyFactory<?>>();

MyBatis初始化过程中读取映射配置文件以及Mapper接口中的注解信息,并 调 用 MapperRegistry.addMapper()方 法 填 充 MapperRegistry.knownMappers 集 合 , 该 集 合 的 key 是 Mapper接口对 应 的Class对 象,valueMapperProxyFactory工厂 对 象,可以为 Mapper接口创 建代理对 象。MapperRegistry.addMapper()方法的 部分实 现 如下:

public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {    // 检测type是否为接口
    if (hasMapper(type)) {
      // 如果已经添加果该接口,则直接抛出异常
      throw new BindingException("...");
    }
    boolean loadCompleted = false;
    try {
      // Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
      knownMappers.put(type, new MapperProxyFactory<T>(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.
      // XML解析和注解处理
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

在需要执 行某SQL语 句时 ,会 先调 用MapperRegistry.getMapper()方法获 取实 现 了 Mapper 接口的代理对 象,例如本节 开 始的示例中,session.getMapper(BlogMapper.class)方法得到的实 际 上 是 MyBatis通 过 JDK动 态 代 理BlogMapper接 口 生 成 的 代 理 对 象 。MapperRegistry.getMapper() 方法的代码 如下。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // 查找指定type的MapperProxyFactory对象
  final MapperProxyFactory<T> mapperProxyFactory 
    = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("...");
  }
  try {
    // 创建实现了type接口的代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

MapperProxyFactory

MapperProxyFactory主要负 责 创 建代理对 象,其中核心字段的含义 和功能如下

// 当前MapperProxyFactory对象可以创建实现了mapperlnterface接口的代理对象
private final Class<T> mapperInterface;

// 缓存,key是mapperlnterface接口中某方法对应的Method对象,value是对应的MapperMethod对象
private final Map<Method, MapperMethod> methodCache 
  = new ConcurrentHashMap<Method, MapperMethod>();

MapperProxyFactory.newInstance()方法实现了创建实现了mapperlnterface 接口的代理对象的功能,具体代码如下:

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

public T newInstance(SqlSession sqlSession) {
  // 创建MapperProxy对象,每次调用都会创建新的MapperProxy对象
  final MapperProxy<T> mapperProxy 
    = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

MapperProxy

MapperProxy实现了InvocationHandler接口,InvocationHandler是实现JDK代理对象的核心逻辑。

MappProxy中核心字段含义和功能:

// 记录了关联的SqlSession对象
private final SqlSession sqlSession;

// mapper接口对应的class对象
private final Class<T> mapperInterface;

// 用于缓存MapperMethod对象,其中key是Mapper接口中方法对应的Method对象,value是对应的MapperMethod对象
// MapperMethod对象会完成参数转换以及SQL语句的执行功能
// MapperMethod中并不记录任何状态相关的信息,所以可以在多个代理对象之间共享
private final Map<Method, MapperMethod> methodCache;

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

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 (isDefaultMethod(method)) {
      // 这里是针对java7以上版本动态类型语言的支持
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 从缓存中获取MapperMethod对象,如果缓存中没有,则创建新的MapperMethod对象并添加到缓存中
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  // 执行SQL语句
  return mapperMethod.execute(sqlSession, args);
}

MapperProxy.cachedMapperMethod()方法主要负责维护methodCache这个缓存集合:

private MapperMethod cachedMapperMethod(Method method) {
  // 先从缓存中获取MapperMethod对象
  MapperMethod mapperMethod = methodCache.get(method);
  if (mapperMethod == null) {
    // 缓存就爱你mapperMethod对象并放到缓存中
    mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
    methodCache.put(method, mapperMethod);
  }
  return mapperMethod;
}

MapperMethod

MapperMethod中封装了 Mapper接口中对应方法的信息,以及对应 SQL语句的信息。

可以将 MapperMethod看作连接 Mapper接口以及映射配置文件中定义的SQL语句的桥梁。

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

// 记录SQL语句和类型
private final SqlCommand command;

// Mapper接口对应方法的信息
private final MethodSignature method;

SqlCommand

SqlCommandMapperMethod中定义 的内 部类 ,它 使用name字段记 录 了 SQL语 句的名称 , 使用type字 段 (SqlCommandType类 型)记 录 了 SQL语 句的类 型。

SqlCommandType是枚举类型 ,有效取值为UNKNOWNINSERTUPDATEDELETESELECTFLUSH

SqlCommand的构造方法会初始化name字段和type字段,代码如下:

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) { // @Flush处理
      name = null;
      type = SqlCommandType.FLUSH;
    } else {
      throw new BindingException("...");
    }
  } else {
    name = ms.getId();
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

private MappedStatement resolveMappedStatement(
  Class<?> mapperInterface, String methodName,
  Class<?> declaringClass, Configuration configuration) {
  // SQL语句的名称是由Mapper接口的名称与对应的方法名称组成的
  String statementId = mapperInterface.getName() + "." + methodName;
  if (configuration.hasStatement(statementId)) { // 检测是否有该名称的SQL语句
    // 从Configuration.mappedStatements集合中查找对应的MappedStatement对象,
    // MappedStatement对象中封装了SQL语句相关的信息,在MyBatis初始化时创建,后面详细描述
    return configuration.getMappedStatement(statementId);
  } 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;
  }
}

ParamNameResolver

MethodSignature中 ,会 使 用 ParamNameResolver处 理 Mapper接 口 中 定 义 的 方 法 的 参 数 列 表 。

ParamNameResolver 使用 name 字 段 (SortedMap<Integer, String>类 型 )记 录 了 参 数 在 参
数 列表中的位置索引与 参 数 名称 之间 的对 应 关 系,其中key表示参 数 在参 数 列表中的索引位置, value表示参 数 名称 ,参 数 名称 可以通过 @Param注解指定,如果没 有指定@Param注解,则 使 用参 数 索引作为 其名称 。

如果参 数 列表中包含RowBounds类 型 或 ResultHandler类 型的参 数 , 则 这 两 种 类 型的参 数 并 不会 被记 录 到name集合中,这 就会 导 致参 数 的索引与 名称 不一致。

image-20200610153922096

ParamNameResolverhasParamAnnotation字 段 (boolean类 型 )记 录 对 应 方 法 的 参 数 列 表 中是否使用了 @Param注 解 。

ParamNameResolver的构 造方法中,会 通过 反射的方式读 取Mapper接口中对 应 方法的信息,并初始化以上字段:

public ParamNameResolver(Configuration config, Method method) {
  // 获取参数列表中每个参数的类型
  final Class<?>[] paramTypes = method.getParameterTypes();
  // 获取参数列表上的注解
  final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  // 记录参数索引与参数名称的对应关系
  final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
  int paramCount = paramAnnotations.length;
  // get names from @Param annotations
  for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    if (isSpecialParameter(paramTypes[paramIndex])) {
      // skip special parameters
      // 如果参数是RowBounds类型或ResultHandler类型,则跳过对该参数的分析
      continue;
    }
    String name = null;
    // 遍历参数对应的注解集合
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      if (annotation instanceof Param) {
        // 如果出现过@Param就把hasParamAnnotation初始化为true
        hasParamAnnotation = true;
        // 获取@Param注解指定的参数名称
        name = ((Param) annotation).value();
        break;
      }
    }
    if (name == null) {
      // @Param was not specified.
      if (config.isUseActualParamName()) {
        name = getActualParamName(method, paramIndex);
      }
      if (name == null) {
        // use the parameter index as the name ("0", "1", ...)
        // gcode issue #71
        name = String.valueOf(map.size());
      }
    }
    map.put(paramIndex, name);
  }
  names = Collections.unmodifiableSortedMap(map);
}

names集合主要在ParamNameResolver.getNamedParams()方法中使用,该 方法接收的参 数 是 用户传入的实参列表,并将实参与其对应名称进行关联,具体代码如下:

public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {
    return args[names.firstKey()];
  } else {
    final Map<String, Object> param = new ParamMap<Object>();
    int i = 0;
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
      // ensure not to overwrite parameter named with @Param
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}

MethodSignature

MethodSignature 也 是 MapperMethod中定义 的内 部类 ,其中封装 了 Mapper接口中定义 的方法的相关 信息, MethodSignature中核心字段的含义 如下:

// 返回值类型是否为 Collection类型或是数组类型
private final boolean returnsMany;
// 返回值类型是否为Map类型
private final boolean returnsMap;
// 返回值类型是否为Void类型
private final boolean returnsVoid;
// 返回类型是否为Cursor类型
private final boolean returnsCursor;
// 返回值类型
private final Class<?> returnType;
// 如果返回值类型是Map,则该字段记录了作为key的列名
private final String mapKey;
// 用来标记该方法参数列表中ResultHandler类型参数的位置
private final Integer resultHandlerIndex;
// 用来标记该方法参数列表中RowBounds类型参数的位置
private final Integer rowBoundsIndex;
//  方法对应的ParamNameResolver对象
private final ParamNameResolver paramNameResolver;

MethodSignature的构 造函数 中会 解析相应 的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);
  // 若MethodSignature对 应 方法的返回值 是Map且指定了@MapKey注解,则 使用getMapKey()方法处 理
  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);
}

getUniqueParamIndex()方法的主要功能是查找指定类型的参数在参数列表中的位置。

private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
  Integer index = null;
  final Class<?>[] argTypes = method.getParameterTypes();
  // 遍历MethodSignature对应方法的参数列表
  for (int i = 0; i < argTypes.length; i++) {
    if (paramType.isAssignableFrom(argTypes[i])) {
      if (index == null) { // 记录paramType类型参数在参数列表中的位置索引
        index = i;
      } else {
        // RowBounds和ResultHandler类型的参数只能有一个,不能重复出现
        throw new BindingException("...");
      }
    }
  }
  return index;
}

convertArgsToSqlCommandParam()辅助方法:

// 负责将args[]数组(用户传入的实参列表)转换成SQL语句对应的参数列表,它是通过上面介绍的
public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}

MapperMethod.execute()

MapperMethod中 最核心的方法是execute()方法,它 会 根据SQL语 句的类 型调 用SqlSession对 应 的方法完成数 据 库 操作:

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()方法,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:
      // 处理返回值为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()) { // ...
        result = executeForCursor(sqlSession, args);
      } else { // 处理返回值为单一对象的方法
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
      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("...");
  }
  return result;
}

执 行 INSERTUPDATEDELETE类 型 的 SQL语 句时 ,其执 行结 果都需要经 过MapperMethod.rowCountResult()方 法 处 理 。

SqlSession 中 的 insert()等 方 法 返 回 的 是 int 值 , rowCountResult()方法会 将 该 int值 转 换 成Mapper接口中对 应 方法的返回值 ,具体 实 现 如下:

private Object rowCountResult(int rowCount) {
  final Object result;
  if (method.returnsVoid()) {
    result = null;
  } else if (Integer.class.equals(method.getReturnType()) 
             || Integer.TYPE.equals(method.getReturnType())) {
    result = rowCount;
  } else if (Long.class.equals(method.getReturnType()) 
             || Long.TYPE.equals(method.getReturnType())) {
    result = (long)rowCount;
  } else if (Boolean.class.equals(method.getReturnType()) 
             || Boolean.TYPE.equals(method.getReturnType())) {
    result = rowCount > 0;
  } else {
    throw new BindingException("...");
  }
  return result;
}

如 果 Mapper接口中定义 的方法准备 使用ResultHandler处 理查 询 结 果集,则 通过 MapperMethod.executeWithResultHandler()方法处 理,具体 实 现 如下:

private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
  // 获 取SQL语 句对 应 的MappedStatement对象
  MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
  if (!StatementType.CALLABLE.equals(ms.getStatementType())
      && void.class.equals(ms.getResultMaps().get(0).getType())) {
    throw new BindingException("...");
  }
  Object param = method.convertArgsToSqlCommandParam(args);
  if (method.hasRowBounds()) {
    RowBounds rowBounds = method.extractRowBounds(args);
    sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
  } else {
    // 调用SqlSession.select()方法,执行查询 ,并由指定的ResultHandler处理结果对象
    sqlSession.select(command.getName(), param, method.extractResultHandler(args));
  }
}

如 果 Mapper接口中对 应 方法的返回值 为 数 组 或是Collection接口实 现 类 ,则 通过 MapperMethod.executeForMany()方 法 处 理 ,具 体 实 现 如 下 :

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  List<E> result;
  Object param = method.convertArgsToSqlCommandParam(args);
  if (method.hasRowBounds()) {
    RowBounds rowBounds = method.extractRowBounds(args);
    // 调用SqlSession.selectList()方法完成查询
    result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
  } else {
    result = sqlSession.<E>selectList(command.getName(), param);
  }
  // issue #510 Collections & arrays support
  if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    if (method.getReturnType().isArray()) {
      return convertToArray(result);
    } else {
      return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    }
  }
  return result;
}

convertToDeclaredCollection()方 法 和 convertToArray()方 法 的 功 能 类 似 ,主 要 负 责 将 结 果 对 象转 换 成Collection集合对 象和数 组 对 象,具体 实 现 如下:

private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {

  // 通过反射创建集合对象
  Object collection = config.getObjectFactory().create(method.getReturnType());
  // 创建MetaObject对象
  MetaObject metaObject = config.newMetaObject(collection);
  metaObject.addAll(list);
  return collection;
}

@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
  Class<?> arrayComponentType = method.getReturnType().getComponentType();
  Object array = Array.newInstance(arrayComponentType, list.size());
  if (arrayComponentType.isPrimitive()) {
    for (int i = 0; i < list.size(); i++) {
      Array.set(array, i, list.get(i));
    }
    return array;
  } else {
    return list.toArray((E[])array);
  }
}

如果Mapper接口中对 应 方法的返回值 为 Map类 型,则 通过 MapperMethod.executeForMap() 方法处理,具体实现如下:

private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
  Map<K, V> result;
  Object param = method.convertArgsToSqlCommandParam(args);
  if (method.hasRowBounds()) {
    RowBounds rowBounds = method.extractRowBounds(args);
    result = sqlSession.<K, V>selectMap(
      command.getName(), param, method.getMapKey(), rowBounds);
  } else {
    result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
  }
  return result;
}

executeForCursor()方法与 executeForMap()方法类似,唯一区别就是调 selectCursor()方法。

参考

  • 《MyBatis技术内幕》

  • 部分图片来源——《MyBatis技术内幕》

MyBatis 《MyBatis技术内幕》

相关推荐



版权声明




留言区

文章目录