MapperMethod


title:MapperMethod
date:2017年11月25日22:20:53
categories:Mybatis



我们接着上面继续分析

@Test
    public void testSelect() {
        SqlSession session = null;
        try {
            session = MybatisUtil.getCurrentSession();
            UserDao userDao = session.getMapper(UserDao.class);
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(3);
            list.add(25);

            List<User> userList = userDao.queryList(list);
            System.out.println(JSON.toJSONString(userList));

        } catch (Exception e) {
            // TODO: handle exception
        } finally {
            if (session != null)
                session.close();
        }
    }

我们都这一句代码进行DEBUG

            List<User> userList = userDao.queryList(list);

首先,我们会进入MapperProxy这个类

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //如果方法是Object类自带的方法,比如没有被重写的equals toString, hashcode 等,还是执行原来的方法
    // getDeclaringClass()返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象。
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //如果不是object的自带方法,先去  Map<Method, MapperMethod> methodCache中找是否已经存在这个method了,没有就将method封装成MapperMethod存进mehtodCache中然后返回MapperMethod。

    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //然后执行MapprMehtod的execute方法
    return mapperMethod.execute(sqlSession, args);
  }

我们看看cachedMapperMethod

  private MapperMethod cachedMapperMethod(Method method) {
  //当method是第一次调用时,我们无法在methodCache中得到MapperMethod,我们就得新创建一个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构造方法,初始化了他的两个属性,command和method

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

这两个属性非常的重要,要详细的介绍的话需要大量的篇幅,所以先放在一旁。

我们先看具体的执行方法execute

 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } 
   //很明显我们的SqlCommandType是SELECT类型
   else if (SqlCommandType.SELECT == command.getType()) {
     //是否返回类型是void类型并且Method参数列表中包含resultHandler,具体的判断在文末分析
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } 
     //我们调用的方法的返回类型是List,会走这个分支。我们看下method.returnsMany的具体实现吧。
     //这里的method是MapperMethod的属性MethodSignature对象
     else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
  }

executeForMany

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //这个方法我们非常有必要分析一下,这个方法封装了我们调用的Mapper接口Method的参数列表对应实参
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
   //终于到了我们的DefaultSqlSession的方法了
      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;
  }

convertArgsToSqlCommandParam

public Object convertArgsToSqlCommandParam(Object[] args) {

      final int paramCount = params.size();
   //没有参数 
   if (args == null || paramCount == 0) {
        return null;
      } 
   //有一个参数,而且不是@Param注解修饰的参数
   else if (!hasNamedParameters && paramCount == 1) {
     //直接返回形参对应的实参
        return args[params.keySet().iterator().next()];
      }
   /*
   否则
   多个参数或者一个参数但是使用@param注解
   就要按以下方法操作
   */

   else {
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
          //如果是使用@param注解的,则put的Key是@param(value)里的Value,如果不是,则put的Key就是int类型的,put的Value自然都是Method形参对应的实参(args[entry.getKey()] entry.getKey() Key都是从0-i-1的int类型,代表顺序,结合getParams方法分析就明了,了解了这些,对于Mybatis中传递参数的三种方式也就理解了
          http://blog.csdn.net/shasiqq/article/details/51305666


          param.put(entry.getValue(), args[entry.getKey()]);
          // issue #71, add param names as param1, param2...but ensure backward compatibility
          //但要确保向后兼容性
          final String genericParamName = "param" + String.valueOf(i + 1);
          if (!param.containsKey(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }

这里我们需要了解params对象到底是怎样得来的,他的具体实现是怎样的。

      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
 private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
      final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
      final Class<?>[] argTypes = method.getParameterTypes();
            //如果是@param修饰的,则put的Value是@param(value)中的Value
          //如果不是,则put的Value就是当前parms这个Map的size
      for (int i = 0; i < argTypes.length; i++) {
        if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
          String paramName = String.valueOf(params.size());
          if (hasNamedParameters) {
            paramName = getParamNameFromAnnotation(method, i, paramName);
          }
          params.put(i, paramName);
        }
      }
      return params;
    }

    private String getParamNameFromAnnotation(Method method, int i, String paramName) {
      final Object[] paramAnnos = method.getParameterAnnotations()[i];
      for (Object paramAnno : paramAnnos) {
        if (paramAnno instanceof Param) {
          paramName = ((Param) paramAnno).value();
        }
      }
      return paramName;
    }

下面是分析上述方法时用到的Method和Class类中的方法介绍:

Method

getParameterTypes
public Class<?>[] getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。如果底层方法不带参数,则返回长度为 0 的数组。 

返回:
此对象所表示的方法的参数类型

Class

isAssignableFrom
public boolean isAssignableFrom(Class<?> cls)判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false。如果该 Class 表示一个基本类型,且指定的 Class 参数正是该 Class 对象,则该方法返回 true;否则返回 false。 
特别地,通过身份转换或扩展引用转换,此方法能测试指定 Class 参数所表示的类型能否转换为此 Class 对象所表示的类型。有关详细信息,请参阅 Java Language Specification 的第 5.1.15.1.4 节。 


参数:
cls - 要检查的 Class 对象 
返回:
表明 cls 类型的对象能否赋予此类对象的 boolean 值 
抛出: 
NullPointerException - 如果指定的 Class 参数为 null。
从以下版本开始: 
JDK1.1 

getComponentType
public Class<?> getComponentType()返回表示数组组件类型的 Class。如果此类不表示数组类,则此方法返回 null。 

返回:
如果此类是数组,则返回表示此类组件类型的 Class
从以下版本开始: 
JDK1.1 
另请参见:
Array

hasNamedParams判断Mapper接口中的指定方法中的参数列表中是否有@Param注解修饰

    private boolean hasNamedParams(Method method) {
      boolean hasNamedParams = false;
      /*
      getParameterAnnotations() 
          返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注释的那个数组的数组。

      */
      final Object[][] paramAnnos = method.getParameterAnnotations();
      for (Object[] paramAnno : paramAnnos) {
        for (Object aParamAnno : paramAnno) {
          //看是否参数有@Param注解修饰
          if (aParamAnno instanceof Param) {
            hasNamedParams = true;
            break;
          }
        }
      }
      return hasNamedParams;
    }

  }
getParameterAnnotations
public Annotation[][] getParameterAnnotations()返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注释(应该是注解把)的那个数组的数组。(如果底层方法没有参数,则返回长度为零的数组。如果该方法有一个或多个参数,则为每个不带注释的参数返回长度为零的嵌套数组。)返回数组中包含的注释对象是可序列化的。此方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。 

返回:
表示按声明顺序对此 Method 对象所表示方法的形参进行注释的那个数组的数组
  • 是否Method返回类型是void类型并且Method参数列表中包含resultHandler
//select语句,Method的返回类型如果是void而且参数列表中有参数类型是ResultHandler
if (method.returnsVoid() && method.hasResultHandler()) {
              if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;

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

this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);

getUniqueParamIndex分析

//原来是个很简单的方法就是判断是否参数列表中是否有参数类型为ResultHandler的,有就返回index,没有就是null

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

总结:

1.MapperMethod的execute方法的作用

  • 首先是将Method方法的实参封装成Object对象

  • 然后调用DefaultSqlSession中的方法,将上述的Object对象以及command.getName() (name = ms.getId(); SqlCommand对象中的name即是该Method对应XML配置文件中SQL的的唯一标示)作为参数 传入方法中,上述两个参数是必须要传递的

    本例是使用了selectList方法

        result = sqlSession.<E>selectList(command.getName(), param);

2.Mybatis为什么要设计MapperMethod类呢?

其实经过这一分析,如果熟悉设计模式的人就会发现,这是使用了命令模式。

什么是命令模式呢,命令模式就是让请求发送者与接收者解耦

命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求

在命令模式结构图中包含如下几个角色:
   ● Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
   ● ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。
   ● Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
   ● Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
   命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
   命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的execute()方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。

这里的调用者就是MapperProxy

接受者是DefaultSqlSession

而具体的命令类是MapperMethod

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值