17.Mybatis之代理对象的执行

1.调用 Mapper 方法时,所有请求都会转发到 MapperProxy.invoke(),这是整个流程的入口:

public class MapperProxy<T> implements InvocationHandler, Serializable {
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 步骤1:过滤 Object 类的方法(toString/hashCode 等),直接执行
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 步骤2:缓存 MapperMethod,避免重复解析;执行方法调用
        return cachedMapperMethod(method).execute(sqlSession, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  // 缓存 MapperMethod:key=Method,value=MapperMethod(解析后的方法签名+SQL映射)
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
}

2.核心:MapperMethod.execute () 解析方法并执行 SQL

  • 构造方法核心源码

    public class MapperMethod {
        // 封装 SQL 命令(namespace+id、SQL 类型:SELECT/INSERT/UPDATE/DELETE)
        private final SqlCommand command;
        // 封装方法签名(返回值类型、参数映射、分页/结果处理器等)
        private final MethodSignature method;
    
        // 核心构造方法:解析 Mapper 接口方法 + 绑定 SQL 配置
        public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
            // 子步骤1:解析 SqlCommand(SQL 命令信息)
            this.command = new SqlCommand(config, mapperInterface, method);
            // 子步骤2:解析 MethodSignature(方法元信息)
            this.method = new MethodSignature(config, mapperInterface, method);
        }
    }
    
  • SqlCommand 解析(绑定 SQL 与方法):SqlCommand 是内部类,负责根据「Mapper 接口 + 方法名」匹配 XML / 注解中的 SQL,并确定 SQL 类型。

    public static class SqlCommand {
      private final String name; // 完整SQL标识:namespace + 方法名(如 com.example.mapper.UserMapper.selectById)
      private final SqlCommandType type; // SQL类型:SELECT/INSERT/UPDATE/DELETE/FLUSH
    
      public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
          // 步骤1:获取方法名
          final String methodName = method.getName();
          // 步骤2:获取Mapper接口全限定名(作为namespace)
          final Class<?> declaringClass = method.getDeclaringClass();
          // 步骤3:从Configuration中查找MappedStatement(MyBatis解析XML/注解后存储的SQL映射对象)
          MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
          if (ms == null) {
              // 未找到匹配的SQL,抛绑定异常(常见原因:XML中id写错、namespace不匹配、方法名重载)
              if (method.getAnnotation(Flush.class) != null) {
                  name = null;
                  type = SqlCommandType.FLUSH;
              } else {
                  throw new BindingException("Invalid bound statement (not found): " 
                      + mapperInterface.getName() + "." + methodName);
              }
          } else {
              // 步骤4:封装SQL标识和类型
              name = ms.getId();
              type = ms.getSqlCommandType();
              // 校验:方法注解与SQL类型是否匹配(如@Select注解对应非SELECT类型会报错)
              if (type == SqlCommandType.UNKNOWN) {
                  throw new BindingException("Unknown execution method for: " + name);
              }
          }
      }
    
      // 核心辅助方法:查找MappedStatement(处理方法重载、父接口继承等场景)
      private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, 
                                                    Class<?> declaringClass, Configuration configuration) {
          // 拼接初始key:接口全限定名 + 方法名
          String statementId = mapperInterface.getName() + "." + methodName;
          // 先查当前接口的MappedStatement
          if (configuration.hasStatement(statementId)) {
              return configuration.getMappedStatement(statementId);
          } 
          // 处理方法重载:若当前接口无匹配,查父接口
          else if (mapperInterface.equals(declaringClass)) {
              return null;
          }
          // 遍历父接口递归查找
          for (Class<?> superInterface : mapperInterface.getInterfaces()) {
              if (declaringClass.isAssignableFrom(superInterface)) {
                  MappedStatement ms = resolveMappedStatement(superInterface, methodName, 
                                                             declaringClass, configuration);
                  if (ms != null) {
                      return ms;
                  }
              }
          }
          return null;
      }
    }
    
  • MappedStatement 的定位
    XML / 注解中的每一个 SQL 节点,最终都会被解析为一个 MappedStatement 实例,它是 MyBatis 执行 SQL 的核心依据,贯穿「Mapper 代理调用 → SQL 执行」的全流程。

    • 数据来源:MyBatis 启动时,解析 mybatis-config.xml 中配置的 Mapper 映射文件(或注解),为每个 SQL 节点创建一个 MappedStatement,并缓存到 Configuration 对象中(Configuration 是 MyBatis 的全局配置中心,MappedStatement 以「namespace + id」为 key 存储);

    • 核心作用:为 Executor(执行器)提供执行 SQL 所需的所有信息(SQL 语句、参数映射、结果映射、执行规则等);

    • 关联关系:MapperMethod 中的 SqlCommand.name 就是 MappedStatement 的 id(即 namespace + 方法名),通过这个 id 可从 Configuration 中精准获取对应的 MappedStatement。

  • MapperMethod.execute () 核心执行(分发 SQL 类型 + 执行)

    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      // 步骤1:根据SQL类型分发执行
      switch (command.getType()) {
          case INSERT: {
              // 子步骤1:转换方法参数为SQL可执行参数(如@Param → Map)
              Object param = method.convertArgsToSqlCommandParam(args);
              // 子步骤2:调用SqlSession.insert(),返回受影响行数/自增主键
              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:
              // 子步骤1:处理无返回值+ResultHandler的场景(手动处理结果集)
              if (method.returnsVoid() && method.hasResultHandler()) {
                  executeWithResultHandler(sqlSession, args);
                  result = null;
              } 
              // 子步骤2:返回集合/数组
              else if (method.returnsMany()) {
                  result = executeForMany(sqlSession, args);
              } 
              // 子步骤3:返回Map(@MapKey)
              else if (method.returnsMap()) {
                  result = executeForMap(sqlSession, args);
              } 
              // 子步骤4:返回Cursor
              else if (method.returnsCursor()) {
                  result = executeForCursor(sqlSession, args);
              } 
              // 子步骤5:返回单个对象(如selectById)
              else {
                  Object param = method.convertArgsToSqlCommandParam(args);
                  result = sqlSession.selectOne(command.getName(), param);
                  // 处理Optional返回值(若方法返回Optional,包装结果)
                  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());
      }
      // 步骤2:校验返回值(如返回基本类型但结果为null,抛异常)
      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, args)方法的执行流程

    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        // 处理RowBounds分页(方法参数中有RowBounds时,传入分页参数)
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectList(command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(command.getName(), param);
        }
        // 适配返回值类型(如方法返回数组,将List转为数组)
        if (!method.getReturnType().isAssignableFrom(result.getClass())) {
            if (method.getReturnType().isArray()) {
                return convertToArray(result); // List → 数组
            } else {
                // List → 自定义Collection(如Set)
                return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            }
        }
        return result;
    }
    
    • sqlSession.selectList(command.getName(), param)为啥不需要传入具体sql语句呢?

      • selectList() 接收的核心是 statementId,而非 SQL 字符串 ——SQL 语句早已被封装在 MappedStatement 中,通过 statementId 即可精准获取。
    • 为啥要两次访问 MappedStatement ?

      • 第一次:解析方法时「校验 + 提取元信息」,确保调用的 SQL 存在且类型匹配;
      • 第二次:执行 SQL 时「获取完整实例」,提供执行所需的所有配置(SQL 源、结果映射等);
        且底层是从同一个全局缓存中读取,无性能损耗,是框架分层设计的合理体现。
posted @ 2025-12-08 20:05  那就改变世界吧  阅读(7)  评论(0)    收藏  举报