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 源、结果映射等);
且底层是从同一个全局缓存中读取,无性能损耗,是框架分层设计的合理体现。
-

浙公网安备 33010602011771号