35、MapperMethod映射器方法.

MapperMethod是MyBatis中数据库操作的核心,它封装了SqlSession,负责执行各种数据库操作。MapperMethod通过SqlCommand和MethodSignature对SQL命令和接口方法进行处理。execute()方法根据不同的操作类型调用SqlSession的不同方法,如executeWithResultHandler()、executeForMany()、executeForMap()和selectOne()。对于查询操作,支持RowBounds进行内存分页,但大型项目通常使用分页插件实现物理分页。

MapperMethod:映射器方法

映射器方法是最底层的被调用者,同时也是binding模块中最复杂的内容。它是MyBatis中对SqlSession会话操作的封装,那么这意味着什么呢?意味着这个类可以看做是整个MyBatis中数据库操作的枢纽,所有的数据库操作都需要经过它来得以实现。
我们单独使用MyBatis时,有时会直接操作SqlSession会话进行数据库操作,但是在SSM整合之后,这个数据库操作却是自动完成的,那么Sqlsession就需要被自动执行,那么组织执行它的就是这里的MapperMethod。
SqlSession会作为参数在从MapperRegisty中getMapper()中一直传递到MapperMethod中的execute()方法中,然后在这个方法中进行执行。

字段

““java
/**
* sql语句
*/

private final SqlCommand command;
/**
* 方法签名
*/
private final MethodSignature method;
““
这两个字段类型都是以MapperMethod中的静态内部类的方式定义的,分别表示sql命令与接口中的方法。
这两个字段都是final修饰,表示不可变,即一旦赋值,就永远是该值。

构造器

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

使用构造器对上面的两个字段进行赋值,这个赋值是永久的。

核心方法:execute()

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
        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()) { //如果结果是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("Mapper method '" + command.getName()
                    + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }

param表示的是SQL执行参数,可以通过静态内部类MethodSignature的convertArgsToSqlCommandParam()方法获取。
对SqlSession的增、删、改操作使用了rowCountResult()方法进行封装,这个方法对SQL操作的返回值类型进行验证检查,保证返回数据的安全。
针对SqlSession的查询操作较为复杂,分为多种情况:
- 针对拥有结果处理器的情况:执行executeWithResultHandler(SqlSession sqlSession, Object[] args)方法
这种有结果处理器的情况,就不需要本方法进行结果处理,自然有指定的结果处理器来进行处理,所以其result返回值设置为null。
- 针对返回多条记录的情况:执行executeForMany(SqlSession sqlSession, Object[] args)方法
内部调用SqlSession的selectList(String statement, Object parameter, RowBounds rowBounds)方法
针对Collections以及arrays进行支持,以解决#510BUG
- 针对返回Map的情况:执行executeForMap(SqlSession sqlSession, Object[] args)方法
内部调用SqlSession的selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds)方法
- 针对返回一条记录的情况:执行SqlSession的selectOne(String statement, Object parameter)方法
针对前三种情况返回的都不是一条数据,在实际项目必然会出现需要分页的情况,MyBatis中为我们提供了RowBounds来进行分页设置,在需要进行分页的情况,直接将设置好的分页实例传到SqlSession中即可。只不过这种方式的分页属于内存分页,针对数据量小的情况比较适合,对于大数据量的查询分页并不适合,大型项目中的分页也不会使用这种方式来实现分页,而是采用之后会解析的分页插件来时限物理分页,即直接修改SQL脚本来实现查询级的分页,再不会有大量查询数据占据内存。

四种情况对应的方法源码

//结果处理器
  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (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));
    }
  }

  //多条记录
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    //代入RowBounds
    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;
  }

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

  @SuppressWarnings("unchecked")
  private <E> E[] convertToArray(List<E> list) {
    E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
    array = list.toArray(array);
    return array;
  }

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

SqlCommand

//SQL命令,静态内部类
  public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      String statementName = mapperInterface.getName() + "." + method.getName();
      MappedStatement ms = null;
      if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
        //如果不是这个mapper接口的方法,再去查父类
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        throw new BindingException("Invalid bound statement (not found): " + statementName);
      }
      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;
    }
  }

name和type,前者表示SQL命令的名称,这个名称就是接口的全限定名+方法名称(中间以.连接),后者表示的是SQL命令的类型,无非增删改查四种。
内部类中有一个带参数的构造器用于对字段赋值,里面涉及到了MappedStatement类,这个类封装的是映射语句的信息,在构建Configuration实例时会创建一个Map集合用于存储所有的映射语句,而这些映射语句的解析存储是在构建映射器的时候完成的(MapperBuilderAssistant类中)。
MappedStatement中的id字段表示的是statementName,即本内部类中的name值,所以在第22行代码处直接将id赋值给name

MethodSignature

字段定义:


        private final boolean returnsMany;//是否返回多个多条记录
        private final boolean returnsMap;//是否返回Map
        private final boolean returnsVoid;//是否返回void
        private final boolean returnsCursor;
        private final Class<?> returnType;//返回类型
        private final String mapKey;//@mapKey注解指定的键值
        private final Integer resultHandlerIndex;//结果处理器参数在方法参数列表中的位置下标
        private final Integer rowBoundsIndex;//分页配置参数在方法参数列表中的位置下标
        private final 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);
        }

构造器的参数分别为Configuration与Method,我们的字段的值部分是需要从这两个参数中获取的。
返回类型returnType从Method中获取
hasNamedParameters表示是否存在注解方式定义的参数
获取Method中RowBounds类型参数与ResultHandler类型参数的位置

核心方法

  public Object convertArgsToSqlCommandParam(Object[] args) {
            return paramNameResolver.getNamedParams(args);
        }
public Object getNamedParams(Object[] args) {
     final int paramCount = params.size();
          if (args == null || paramCount == 0) {
            //如果没参数
            return null;
          } else if (!hasNamedParameters && paramCount == 1) {
            //如果只有一个参数
            return args[params.keySet().iterator().next().intValue()];
          } else {
            //否则,返回一个ParamMap,修改参数名,参数名就是其位置
            final Map<String, Object> param = new ParamMap<Object>();
            int i = 0;
            for (Map.Entry<Integer, String> entry : params.entrySet()) {
              //1.先加一个#{0},#{1},#{2}...参数
              param.put(entry.getValue(), args[entry.getKey().intValue()]);
              // issue #71, add param names as param1, param2...but ensure backward compatibility
              final String genericParamName = "param" + String.valueOf(i + 1);
              if (!param.containsKey(genericParamName)) {
                //2.再加一个#{param1},#{param2}...参数
                //你可以传递多个参数给一个映射器方法。如果你这样做了, 
                //默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
                //如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。 
                param.put(genericParamName, args[entry.getKey()]);
              }
              i++;
            }
            return param;
          }
}

其核心功能就是convertArgsToSqlCommandParam(Object[] args)方法,用于将方法中的参数转换成为SQL脚本命令中的参数形式,其实就是将参数位置作为键,具体的参数作为值保存到一个Map集合中,这样在SQL脚本命令中用键#{1}通过集合就能得到具体的参数。
之后会介绍,在params集合中保存的键值对分别为未排除RowBounds类型和ResultHandler类型的参数时各参数的位置下标与排除之后按原参数先后顺序重新编号的新位置下标,而params的size大小其实是排除RowBounds类型和ResultHandler类型的参数之后参数的数量大小。
上面的源码第14行,以params中的值(即新位置下标)为键,以通过原位置下标从方法的参数数组中获取的具体参数为值保存在param集合中。之后再以paramn(n为从1开始的数值)为键,以通过原位置下标从方法的参数数组中获取的具体参数为值保存在param集合中,这么一来,在param中就保存这两份参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值