编程语言
首页 > 编程语言> > java架构师学习路线-揭示Mapper类背后的执行逻辑(下)

java架构师学习路线-揭示Mapper类背后的执行逻辑(下)

作者:互联网

图灵学院  java架构师学习路线

 

NO.2|通过Mapper代理对象调用方法

 

那么在调用mapper.findUser(user)又会发生什么呢,相信你心里也有了一些猜想。首先,这里的mapper是一个代理对象,所以在通过代理对象调用方法的时候一定会调用MapperProxy的invoke方法,我们大胆猜测,mybatis在invoke方法中做了处理,调用了sqlSession的方法,从而可以进行SQL的执行。具体是不是这样呢,还要继续向下探究。

可以肯定的是通过mapper代理对象调用方法的时候,一定会走MapperProxy的invoke方法,所以这里是执行的入口,探究之旅也从这里开始。

可以看见在MapperProxy的invoke方法中,调用了MapperMethod的execute(sqlSession, args)方法。

 

public class MapperProxy<T> implements InvocationHandler, Serializable:

 

private final Map<Method, MapperMethod> methodCache;

 

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    try {

      if (Object.class.equals(method.getDeclaringClass())) {

        return method.invoke(this, args);

      } else if (isDefaultMethod(method)) {

        return invokeDefaultMethod(proxy, method, args);

      }

    } catch (Throwable t) {

      throw ExceptionUtil.unwrapThrowable(t);

    }

    final MapperMethod mapperMethod = cachedMapperMethod(method);

    // 执行sqlsession的方法

    return mapperMethod.execute(sqlSession, args);

}

 

private MapperMethod cachedMapperMethod(Method method) {

    //判断methodCache中以method为key有没有值存在, 如果没有创建一个MapperMethod对象放进去

    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

}

 

MapperMethod是整个代理机制的核心类,我们一直心心念念的sqlSession的操作就是在这个类中进行了封装。所以有必要先来认识一下这个类以及它的成员变量,方便我们后续的理解。

它的成员变量有两个,一个是SqlCommand【注2】命令类型,还有一个是MethodSignature【注3】方法签名。

 

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, mapperInterface, method);

  }

  

}

 

【注2】:SqlCommand命令类型,SqlCommand的构造方法中会根据mapperInterface.getName() + "." + method.getName()生成statementId,然后根据statementId从configuration中取出对应的MappedStatement(每个sql语句会被封装成一个MappedStatement),进而通过ms.getSqlCommandType()获得此次SQL命令的类型—增、删、改、查。

 

【注3】:MethodSignature方法签名,里面定义了关于方法的相关信息,具体是什么可以看下面的注释部分。

 

public MethodSignature(Configuration configuration, Method method) throws BindingException {

      this.returnType = method.getReturnType();      // 返回值类型

      this.returnsVoid = void.class.equals(this.returnType);      // 返回值是否为void

      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());      // 是否返回多个结果

      this.mapKey = getMapKey(method);      // method是否使用了mapKey注解

      this.returnsMap = (this.mapKey != null);       // 返回值是否是Map类型

      this.hasNamedParameters = hasNamedParams(method); // 参数是否自定义了别名

      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); // 是否使用mybatis的物理分页

      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); // 是否有resutlHandler

      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters)); // 请求参数解析

    }

 

认识了MapperMethod这个类之后,就要探究它的execute方法的执行逻辑了。在execute方法中,会先判断SQL语句的命令类型—增、删、改、查。选择不同的执行逻辑。

 

而增删改查执行逻辑大致相同,先是调用method.convertArgsToSqlCommandParam(args)进行参数转换(在介绍参数处理的整个流程的时候会详细介绍)然后就是调用sqlSession对应的增删改方法。

 

而当进行查询操作的时候,可以看到根据返回类型的不同,会执行不同的方法,然而殊途同归,最终都会先调用convertArgsToSqlCommandParam进行参数转化,然后调用sqlSession的不同的查询api。

 

我们这里仅以需要返回多个结果,也就是返回参数是List的情况为例,进行探究。当返回结果为List时,会调用executeForMany(sqlSession, args)方法。

 

public class MapperMethod:

 

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

          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;

  }

 

可以清楚的看见在executeForMany方法中, 进行了参数转换后,调用了sqlSession.selectList()得到结果,最后根据method定义的返回类型进行数据封装。终于,我们在Mapper代理对象执行方法的过程中,成功的找到了sqlSession,可以用它与数据库交互了。

 

  public class MapperMethod:

  

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

    }

    if (!method.getReturnType().isAssignableFrom(result.getClass())) {

      if (method.getReturnType().isArray()) {

        return convertToArray(result);

      } else {

        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);

      }

    }

    return result;

  }

 

NO.3|小结

 

mybatis在 session.getMapper(UserMapper.class); 的时候获取代理对象工厂,使用代理对象工厂创建MapperProxy,并利用MapperProxy创建JDK动态代理对象,然后返回代理对象。

 

mybatis在 mapper.findUser(user);  执行的时候,其实是用代理对象来执行方法,在执行的过程中,会调用MapperProxy的invoke方法,invoke方法中会通过MapperMethod调用sqlSession的方法操作SQL。

 

截止到这里,当调用mapper.findUser(user)时mybatis在干什么?相信大家已经清楚了。

尽管Java架构师学习路线已经分享给大家,但有多少人能认真的去践行,这个就难说了。互联网寒冬已经到来,作为程序员,更应在此时提高自己,有着更高远的追求。

篇幅有限,如果需要更详细的java架构师学习路线资料可加博主qq:1993712276,或者去图灵官网查看

标签:Mapper,Object,java,args,sqlSession,result,架构师,MapperMethod,method
来源: https://www.cnblogs.com/tulingxueyuan/p/13598552.html