Mybatis源码(3)- MapperProxy与MapperMethod

1.前言

前面<<Mybatis源码2>>分析了事务工厂的加载,数据源的加载,以及mapper的加载。那么在加载完成了以后,我们就开始后续的步骤。

这里再放一下mybatis查询的步骤:

        LearnMybatisApplication application = new LearnMybatisApplication();
        // 1.读取xml内容
        @Cleanup InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        // 2.读取配置,生成创建工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 3.生成SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 4.获取Mapper对象
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);

        // 5.调用方法,查询数据库
        List<User> users = mapper.list("jiang");

2.openSession

进入第三步的openSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //通过事务工厂来产生一个事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //生成一个执行器(事务包含在执行器里)
      final Executor executor = configuration.newExecutor(tx, execType);
      //然后产生一个DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      //如果打开事务出错,则关闭它
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      //最后清空错误上下文
      ErrorContext.instance().reset();
    }
  }

这里最终是会生成一个DefaultSqlSession,主要是将TransactionFactoryExecutor组装到里面,TransactionFactory的话,前面已经分析了,而Executor则先不分析这里面的具体实现,放一放,等后面介绍到Executor再分析Executor。
主要是知道我们获取到了一个DefaultSqlSession对象。

3.sqlSession.getMapper

IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);这一步会去DefaultSqlSession对象里面获取IUserMapper的代理对象。
进入getMapper,可以看到调用的是configuration的getMapper

  public <T> T getMapper(Class<T> type) {
    //最后会去调用MapperRegistry.getMapper
    return configuration.<T>getMapper(type, this);
  }

再进入configuration的getMapper,发现是调用MapperRegistry.getMapper,那我们直接到MapperRegistry.getMapper

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    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还记得吗,就是上篇文章中,根据IUserMapper类型存入MapperProxyFactory对象的Map,所以这里就会获取出MapperProxyFactory,然后调用它的newInstance生成代理对象MapperProxy。

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //用JDK自带的动态代理生成映射器
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

4.调用IUserMapper的list

在我们获取到了IUserMapper的代理对象MapperProxy,然后调用list方法,所以自然会进MapperProxy的invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
    //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //这里优化了,去缓存中找MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行
    return mapperMethod.execute(sqlSession, args);
  }

首先如果是Object的方法,则直接调用,然后返回,不走下面的逻辑。
然后去获取MapperMethod这个对象,获取到了再调用execute,所以这个MapperMethod就是这次要分析的核心。
这里是如果缓存中有MapperMethod就从缓存中拿,没有就会自己创建。

5.MapperMethod

这个类的定义:

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

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

  //执行
  public Object execute(SqlSession sqlSession, Object[] args) {
    xxxxxx
  }
// xxxxxx

可以看到也比较简单,就是两个成员变量SqlCommandMethodSignature,这个类主要的作用就是:
1.解析我们传给sql需要的参数,变成一个Map,传递给后面的执行器
2.调用SqlSession的方法
3.对SqlSession方法返回的值简单的处理一下,用的较少

5.1 MapperMethod的创建

那么先看它的创建,也就是构造函数。

SqlCommand

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

这个SqlCommand会保存着statementId,这里是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,这个后续也会被用到,用于定位是哪个MappedStatement?;褂斜4孀乓桓鰐ype就是这个sql是什么类型,比如这里就是SELECT。
里面的逻辑不复杂,就不看了。

MethodSignature

这个类定义:

  //方法签名,静态内部类
  public static class MethodSignature {

    private final boolean returnsMany;
    private final boolean returnsMap;
    private final boolean returnsVoid;
    private final Class<?> returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final SortedMap<Integer, String> params;
    private final boolean hasNamedParameters;
   // xxxxx
}

这个类主要作用:
1.初始化一些对SqlSession方法返回值处理的配置
2.解析@Param,获取出里面的值传递给后面的SqlSession

那我们分析这个是怎么去解析@Param的。
先看MethodSignature的构造函数

    public MethodSignature(Configuration configuration, Method method) {
      this.returnType = method.getReturnType();
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      this.mapKey = getMapKey(method);
      this.returnsMap = (this.mapKey != null);
      // 判断是否有@Param
      this.hasNamedParameters = hasNamedParams(method);
      //以下重复循环2遍调用getUniqueParamIndex,是不是降低效率了
      //记下RowBounds是第几个参数
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      //记下ResultHandler是第几个参数
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 解析@Param
      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
    }

hasNamedParams(method)的代码:

private boolean hasNamedParams(Method method) {
      boolean hasNamedParams = false;
      final Object[][] paramAnnos = method.getParameterAnnotations();
      for (Object[] paramAnno : paramAnnos) {
        for (Object aParamAnno : paramAnno) {
          if (aParamAnno instanceof Param) {
            //查找@Param,有一个则返回true
            hasNamedParams = true;
            break;
          }
        }
      }
      return hasNamedParams;
    }

遍历这个方法上面的是否有@Param注解,有就返回true

然后到Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));getParams:

    //得到所有参数
    private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
      //用一个TreeMap,这样就保证还是按参数的先后顺序
      final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
      final Class<?>[] argTypes = method.getParameterTypes();
      for (int i = 0; i < argTypes.length; i++) {
        //是否不是RowBounds/ResultHandler类型的参数
        if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
          //参数名字默认为0,1,2,这就是为什么xml里面可以用#{1}这样的写法来表示参数了
          String paramName = String.valueOf(params.size());
          if (hasNamedParameters) {
            //还可以用注解@Param来重命名参数
            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;
    }

这里可以看到getParams里面用了一个SorteMap来存放参数下标和参数名,这里有一点,如果我们的参数不使用@Param,那么存在SorteMap的Key就会是数字从0一直递增,值也是从0一直递增,如果指定了@Param,那value就会变成里面@Param里value的值。

举个例子:
如果我们的Mapper是这样写的:

public interface IUserMapper {
    List<User> list(@Param(value = "name") String name);
}

那么存在SorteMap的就会是这样的Key为0,值为name。
如果没有@Param(value = "name"),那么存在SorteMap的就会是这样的Key为0,值为0

5.2 execute

在创建好了MapperMethod后,就开始调用execute方法了。

 //执行
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
    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));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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 {
        //否则就是一条记录
        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;
  }

这里就对应着增删改查操作,我们这里是select,并且返回的结果是List,那么就会进入executeForMany

 //多条记录
  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;
  }

这里关键就是:
1.MethodSignature的convertArgsToSqlCommandParam
2.sqlSession.selectList

那这个convertArgsToSqlCommandParam干了什么呢?

  public Object convertArgsToSqlCommandParam(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;
      }
    }

上面的代码就可以发现,它根据之前的params,也就是之前的SorteMap:

0 -> 'name'

遍历这个map,然后获取第一个传入的参数,由于我们是mapper.list("jiang");调用的,所以这个参数就会是jiang
它首先将name -> jiang加入,然后再加入一个param1 - jiang,然后返回这个param
所以我们得到一个这样的Map:

image.png

那么获取到了这个param后,就是我们的最后一步

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

这里的commond.getName就是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,然后param就是我们刚刚convertArgsToSqlCommandParam的返回值。

这样MapperMethod就将我们传递的参数封装好,并且将statementId一并传入到SqlSession的selectList中,后面就可以获取到对应的MappedStatement,以及sql的参数,去Mysql中查询

6.总结

**1.DefaultSqlSession通过getMapper,去MapperRegistry的knownMappers中获取出了Mapper的代理对象工厂MapperProxyFactory,然后创建出了MapperProxy

2.MapperMethod对象初始化的时候构建了SqlCommand以及MethodSignature,分别是存储了statementId也就是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,以及将传入的参数根据@Param看解析转成Map,例如:[name: "jiang"],这里用json表示一下

3.将com.learn.mybatis.learnmybatis.mapper.IUserMapper.list以及参数传给了sqlSession的selectList,然后开始后续的流程**

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容