Mybatis源码学习(四)自定义Mapper方法执行流程

前言

虽然SqlSession提供了CRUD的各种方法,但是操作起来还是比较繁琐,mybatis提供了更好的方法,就是我们此章要详解介绍的
Mapper

上述的各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐,它们并不符合类型安全,对你的 IDE 和单元测试也不是那么友好。因此,使用映射器类来执行映射语句是更常见的做法。

1. 简单的栗子,接口类的调试

1.1 代码截图

测试类
在这里插入图片描述
Mapper.xml文件
在这里插入图片描述
Mapper接口
在这里插入图片描述

1.2代码调试跟踪

刚开始学习的同学可能会在调试被代理接口时不知如何操作,介绍一个使用的方法进行调试

step1 接口调用处断点

从截图中我们可以发现此时获取到的mapper是一个MapperProxy对象,有动态代理基础的应该知道,代理类都是通过执行invoke() 方法进行代理的
在这里插入图片描述

step2 找到相应的代理类的invoke()方法,进行断点

通过在代理类的invoke() 方法上断点,我们就可以执行后续的调试工作了
在这里插入图片描述

2. MapperProxy介绍

MapperProxy是实现类InvocationHandler接口的类,实现了invoke()方法
在这里插入图片描述

2.1 如何获取MapperProxy对象

DefaultSqlSession

public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

Configuration

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

MapperRegistry

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

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

MapperProxyFactory

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

我们可以看见最终是通过MapperRegistry的内部的一个Map<Class<?>, MapperProxyFactory<?>> knownMappers HashMap获取到Mapper的代理工厂,通过代理工厂的newInstance获取到的MapperProxy.那么knownMappers中何时存放的键值对呢! 通过查看调用链可以发现,在我们通过SqlSessionFactoryBuilderbuild方法创建SqlSessionFactory时就已经把MapperMapper的代理工厂放入到HashMap中了.
在这里插入图片描述

2.2 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 {
        //非Object类,从缓存中找到Method进行调用
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
}

cachedInvoker()

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
      // It should be removed once the fix is backported to Java 8 or
      // MyBatis drops Java 8 support. See gh-1929
      //Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
      //methodCache 是一个ConcurrentHashMap<Method, MapperMethodInvoker>
      //先从methodCache 中获取
      MapperMethodInvoker invoker = methodCache.get(method);
      if (invoker != null) {
      	//获取到直接返回
        return invoker;
      }
	  
	  //
      return methodCache.computeIfAbsent(method, m -> {
        //接口中的default修饰的方法
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          //非default方法返回一个PlainMethodInvoker
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
}

PlainMethodInvoker 内部维护了一个MapperMethod,在调用PlainMethodInvokerinvoke()时会最终调用MapperMethodexecute方法

interface MapperMethodInvoker {
  Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}

private static class PlainMethodInvoker implements MapperMethodInvoker {
   private final MapperMethod mapperMethod;

   public PlainMethodInvoker(MapperMethod mapperMethod) {
     super();
     this.mapperMethod = mapperMethod;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
     return mapperMethod.execute(sqlSession, args);
   }
}

3. MapperMethod介绍

3.1 内部结构

一个公有构造方法和一个公共方法execute()
在这里插入图片描述

3.2 execute()详解

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断何种类型的sql
    switch (command.getType()) {
      case INSERT: {
      	//把方法参数转换成sql语句中的参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //调用SqlSession执行命令,并包装结果
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
      	//把方法参数转换成sql语句中的参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //调用SqlSession执行命令,并包装结果
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
      	//把方法参数转换成sql语句中的参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //调用SqlSession执行命令,并包装结果
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
      	//改方法没有返回值时,并且方法中有ResultHandler
        if (method.returnsVoid() && method.hasResultHandler()) {
          //使用ResultHandler的方式调用
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          //方法返回数组获集合时,使用数组方式调用
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          //返回Map时使用Map方式调用
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          //使用游标的方式调用
          result = executeForCursor(sqlSession, args);
        } else {
          //把方法参数转换成sql语句中的参数
          Object param = method.convertArgsToSqlCommandParam(args);
          //调用SqlSession的selectOne
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
}
convertArgsToSqlCommandParam() 转换参数列表为Sql命令的参数
public Object convertArgsToSqlCommandParam(Object[] args) {
   return paramNameResolver.getNamedParams(args);
}
public Object getNamedParams(Object[] args) {
	//获取Param注解标记的参数个数
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      //没有参数直接返回
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      //没有Param注解,并且只有一个参数
      Object value = args[names.firstKey()];
      //包装成Map返回
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      //遍历参数的Map,取出参数数组中对应名称的参数,名称从names中获得
      //放入map中
      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 + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
}
ParamNameResolver构造方法, 初始化时解析Mapper方法中Param注解对应的参数
public ParamNameResolver(Configuration config, Method method) {
    this.useActualParamName = config.isUseActualParamName();
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    //从Param注解标记的参数获取所有的参数名
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        if (useActualParamName) {
          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
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
}
增删改时的返回行数处理
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())) {
      //返回Integer时,直接返回
      result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      //long包装
      result = (long) rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      //boolean包装
      result = rowCount > 0;
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
}
executeWithResultHandler()无返回值的查询并且参数中有ResultHandler
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
	//获取MappedStatement
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    //mapper的方法的返回值必须是void,但是xml或者注解上必须提供返回类型
    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.");
    }
    //把方法参数转换成sql语句中的参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      //如果有行数限制,解析分页参数
      RowBounds rowBounds = method.extractRowBounds(args);
	  //调用sqlSession的select方法带上分页的
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      //调用sqlSession的select方法
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
}
executeForMany()
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //把方法参数转换成sql语句中的参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      //有分页时
      RowBounds rowBounds = method.extractRowBounds(args);
      //调用sqlSession的分页list查询
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      //调用无分页的list查询
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    //如果返回不是List
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      //返回数组时
      if (method.getReturnType().isArray()) {
      	//转换成数组
        return convertToArray(result);
      } else {
      	//转换成方法上声明的Collection
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
}
executeForMap()
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
    Map<K, V> result;
    //把方法参数转换成sql语句中的参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      //有分页时
      RowBounds rowBounds = method.extractRowBounds(args);
      //调用sqlSession的分页map查询
      result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
      //调用sqlSession的map查询
      result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
}
executeForCursor()
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
    Cursor<T> result;
    //把方法参数转换成sql语句中的参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      //有分页时
      RowBounds rowBounds = method.extractRowBounds(args);
      //调用sqlSession的分页cursor查询
      result = sqlSession.selectCursor(command.getName(), param, rowBounds);
    } else {
      //调用sqlSession的cursor查询
      result = sqlSession.selectCursor(command.getName(), param);
    }
    return result;
}

总结

本章主要是学习了自定义Mapper的执行流程.
首先从SqlSession中获取到Mapper的代理类MapperProxy,执行自定义Mapper的方法时会调用MapperPorxy的invoke()
MapperPorxy内部会通过MapperMethod和执行类型和返回值来调用SqlSession的对应方法进行处理

流程图如下
在这里插入图片描述

喜欢的小伙伴请动动小手关注和点赞吧,也可留言一起探讨怎样更好的学习源码!!!

文章链接

Mybatis源码学习(一)初探执行流程
Mybatis源码学习(二)配置文件解析到SqlSessionFactory构建
Mybatis源码学习(三)SqlSession详解
Mybatis源码学习(四)自定义Mapper方法执行流程
Mybatis源码学习(五)Executor和StatementHandler详解
Mybatis源码学习(六)结果集自动封装机制
Mybatis源码学习(七)mybatis缓存详解
Mybatis源码学习(八)Mybatis设计模式总结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值