Mybatis源码学习(15)-binding模块之MapperMethod类

导读:本篇文章讲解 Mybatis源码学习(15)-binding模块之MapperMethod类,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、概述
1、Mybatis操作数据

  在Mybatis中,进行数据操作时,有两种方式,分别是:1、使用传统的MyBatis提供的API;2、使用Mapper接口,即面向接口编程。

  1. 传统的Mybatis工作模式
    示例:
    @Test
	public void testApi() {
		SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自动提交
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("field1", "a1");
		List<Map<String, Object>> list = sqlSession.selectList("TestMapper.queryList", param);
		System.out.println(list.toString());
	}

在这里插入图片描述
2. 面向接口编程
示例:

@Test
	public void testMapper() {
		SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自动提交
		TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("field1", "a1");
		List<Map<String, Object>> list = testMapper.queryList(param);
		System.out.println(list.toString());
	}

在这里插入图片描述

2、面向接口编程简介

  Mybatis的binding模块就是用来实现面向接口编程的功能。面向接口编程,其实就是通过建立Mapper接口与XML配置文件的一一对应关系,从而实现通过操作对应接口来实现操作数据库SQL语句实现增删改查功能。

二、binding模块结构

Mybatis的binding模块的包目录:org.apache.ibatis.binding。具体包结构如下图所示:
在这里插入图片描述
其中,Mybatis的binding模块就是用到了动态代理。

  • MapperMethod
    定义Mapper接口中对应方法的信息以及对应SQL语句的信息,用来完成参数转换以及SQL语句的执行功能,可以看成连接Mapper接口以及映射配置文件中定义的SQL语句的桥梁。
  • MapperProxy
    MapperProxy实现了lnvocationHandler接口,是代理对象。
  • MapperProxyFactory
    负责创建代理对象
  • MapperRegistry
    Mapper接口及其对应的代理对象工厂的注册中心
三、MapperMethod类

  定义Mapper接口中对应方法的信息以及对应SQL语句的信息,用来完成参数转换以及SQL语句的执行功能,可以看成连接Mapper接口以及映射配置文件中定义的SQL语句的桥梁。

  1. 字段、构造函数
    在MapperMethod类中,定义了command、method两个字段,这两个字段均是内部类对象。其中command定义SQL命令的概要信息,主要包括了SQL命令的名称和类型;method定义Mapper接口中方法的相关信息。构造函数主要是实现command、method两个对象的初始化。
  private final SqlCommand command; //定义SQL命令的概要信息,主要包括了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);
  }
  1. 内部类 SqlCommand
    MapperMethod内部类,用来定义SQL命令的概要信息,主要包括了SQL命令的名称和类型。
 public static class SqlCommand {

    private final String name;//定义SQL命令的名称,由Mapper接口的全限定类名与对应的方法名称组成的。
    private final SqlCommandType type;//定义SQL命令的类型,SqlCommandType是枚举类型,可选值为:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;

    /**
     * 构造函数,完成对字段属性name、type的初始化。
     * 同时验证了Configuration.mappedStatements中是否有对应的MappedStatement对象(在解析XML时初始化该对象)。
     * @param configuration
     * @param mapperInterface
     * @param method
     */
    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;
    }
    /**
     * 解析SqlCommand实例对应的MappedStatement对象,并返回。
     * 对应的MappedStatement对象在解析Mapper对应的XML时,进行初始化,并存储在了Configuration.mappedStatements中。
     * @param mapperInterface
     * @param methodName
     * @param declaringClass
     * @param configuration
     * @return
     */
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      String statementId = mapperInterface.getName() + "." + methodName;
      if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {//当declaringClass==mapperInterface时,说明methodName对应方法的MappedStatement对象不存在,且不可能是在在超类中,所以直接返回null。
        return null;
      }
      //下面循环是处理当methodName对应的方法在超类中的情况
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {//判断declaringClass是superInterface的父类,如果是继续查找对应的父类,直到查找到对应的MappedStatement或者检索完所有的父类
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }
  1. 内部类 MethodSignature
    定义Mapper接口中方法的相关信息。其中ParamNameResolver类后续再详细分析,现在只需要了解是用来处理方法参数即可。
public static class MethodSignature {

    private final boolean returnsMany;//返回值类型是否为Collection类型或是数组类型
    private final boolean returnsMap;//返回值类型是否为Map类型
    private final boolean returnsVoid;//返回值类型是否为void类型
    private final boolean returnsCursor;//返回值是否为Cursor类型
    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.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语句对应的参数列表
     * @param args
     * @return
     */
    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }

    public boolean hasRowBounds() {
      return rowBoundsIndex != null;
    }

    public RowBounds extractRowBounds(Object[] args) {
      return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
    }

    public boolean hasResultHandler() {
      return resultHandlerIndex != null;
    }

    public ResultHandler extractResultHandler(Object[] args) {
      return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
    }

    public String getMapKey() {
      return mapKey;
    }

    public Class<?> getReturnType() {
      return returnType;
    }

    public boolean returnsMany() {
      return returnsMany;
    }

    public boolean returnsMap() {
      return returnsMap;
    }

    public boolean returnsVoid() {
      return returnsVoid;
    }

    public boolean returnsCursor() {
      return returnsCursor;
    }
    /**
     * 查找指定类型的参数在参数列表中的位置,且该paramType在参数中只能有一个,否则会抛出BindingException异常。
     * @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 {
            throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
          }
        }
      }
      return index;
    }

    private String getMapKey(Method method) {
      String mapKey = null;
      if (Map.class.isAssignableFrom(method.getReturnType())) {
        final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
        if (mapKeyAnnotation != null) {
          mapKey = mapKeyAnnotation.value();
        }
      }
      return mapKey;
    }
  }
  1. 内部类 ParamMap
    重写了HashMap类,主要重写了get()方法,在原来的基础上,添加了当获取不存在的key值时,直接保存BindingException异常。
public static class ParamMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -2212268410512043556L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
      }
      return super.get(key);
    }

  }
  1. execute()方法
    代理对象调用invoke()方法时,最终是调用了execute()方法,是最核心的代码,是实现操作SQL语句的逻辑的地方。这个方法主要用来处理参数、处理返回结果,真正操作数据的操作还是交给了sqlSession对应的方法。
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        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()) {
          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);
          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("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
  1. convertArgsToSqlCommandParam()方法
    负责将args[]数组(用户传入的实参列表)转换成SQL语句对应的参数列表。内部是通过paramNameResolver的getNamedParams()方法实现。
 public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }
  1. 返回结果处理
/**
   * 对数字或布尔类型的返回结果进行转换
   * @param rowCount
   * @return
   */
  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("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }
  /**
   * 处理需要通过回调ResultHandler处理结果集的情况
   * @param sqlSession
   * @param args
   */
  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (!StatementType.CALLABLE.equals(ms.getStatementType())
        && void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName()
          + " needs either a @ResultMap annotation, a @ResultType annotation,"
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    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(command.getName(), param, method.extractResultHandler(args));
    }
  }
  /**
   * 处理对应方法的返回值为数组或是Collection接口实现类
   * @param <E>
   * @param sqlSession
   * @param args
   * @return
   */
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      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;
  }
  /**
   * 处理返回值为Cursor的方法
   * @param <T>
   * @param sqlSession
   * @param args
   * @return
   */
  private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
    Cursor<T> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<T>selectCursor(command.getName(), param);
    }
    return result;
  }
  /**
   * 主要负责将结果对象转换成Collection集合对象和数组对象
   * @param <E>
   * @param config
   * @param list
   * @return
   */
  private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
    Object collection = config.getObjectFactory().create(method.getReturnType());
    MetaObject metaObject = config.newMetaObject(collection);
    metaObject.addAll(list);
    return collection;
  }
  /**
   * 主要负责将结果对象转换成Collection集合对象和数组对象
   * @param <E>
   * @param list
   * @return
   */
  @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);
    }
  }
  /**
   * 主要负责将结果对象转换成Map对象
   * @param <K>
   * @param <V>
   * @param sqlSession
   * @param args
   * @return
   */
  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;
  }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68897.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!