【Mybatis】Mybatis源码之Mapper方法执行流程

269 阅读3分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

时序图

sequenceDiagram
participant A as Client
participant F as PurchaseMapper
participant B as MapperProxy
participant E as MapperMethod
participant C as SqlCommand
participant D as MethodSignature
participant G as ParamNameResolver

A -->> F : findByCondition
A ->> B : invoke
B ->> E : new
E ->> C : new
C -->> E : SqlCommand
E ->> D : new
D ->> G : new
G -->> D : ParamNameResolver
D -->> E : MethodSignature
E -->> B : MapperMethod
B ->> E : execute
E -->> A : ArrayList<Purchase>

详细步骤

Client

QueryCondition condition = new QueryCondition();
condition.setId(1);
condition.setCategory(1);
// 调用mapper方法,执行查询
List<Purchase> purchaseList = mapper.findByCondition(condition);

PurchaseMapper#findByCondition

/**
 * 根据条件查询
 */
List<Purchase> findByCondition(QueryCondition condition);

MapperProxy#invoke

/**
 * 代理方法
 * @param proxy 代理对象
 * @param method 代理方法
 * @param args 代理方法的参数
 * @return 方法执行结果
 * @throws Throwable
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) { // 继承自Object的方法
            // 直接执行原有方法
            return method.invoke(this, args);
        } else if (method.isDefault()) { // 默认方法
            // 执行默认方法
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    /**
     * 1. 以method为key,缓存当前方法对应的MapperMethod
     * 2. 如果存在method key,则返回对应的value
     */
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 调用MapperMethod中的execute方法,执行操作,并获取结果并返回
    return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
    // 有key就返回对应的MapperMethod,没有则创建一个新的MapperMethod
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

MapperMethod

/**
 * MapperMethod的构造方法
 *
 * @param mapperInterface 映射接口
 * @param method          映射接口中的具体方法
 * @param config          配置信息Configuration
 */
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
}

SqlCommand

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    // 方法名称
    final String methodName = method.getName();
    // 方法所在的类,可能是mapperInterface,也可能是mapperInterface的子类
    final Class<?> declaringClass = method.getDeclaringClass();
    // 获取MappedStatement
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
            configuration);
    if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
            name = null;
            type = SqlCommandType.FLUSH;
        } else {
            throw new BindingException("Invalid bound statement (not found): "
                    + mapperInterface.getName() + "." + methodName);
        }
    } else {
        // 获取MappedStatement的ID
        name = ms.getId();
        // 获取SqlCommandType
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + name);
        }
    }
}


/**
 * 找出指定接口指定方法对应的MappedStatement对象
 * @param mapperInterface 映射接口
 * @param methodName 映射接口中具体操作方法名
 * @param declaringClass 操作方法所在的类。一般是映射接口本身,也可能是映射接口的子类
 * @param configuration 配置信息
 * @return MappedStatement对象,MappedStatement完整对应了一条数据库 操作语句
 */
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
                                               Class<?> declaringClass, Configuration configuration) {
    // 数据库操作语句的编号是:接口名.方法名
    String statementId = mapperInterface.getName() + "." + methodName;
    // configuration保存了解析后的所有操作语句,去查找该语句
    if (configuration.hasStatement(statementId)) {
        // 从configuration中找到了对应的语句,返回
        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;
}

MethodSignature

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
    // 获取返回值类型
    Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
    if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
    } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
    } else {
        this.returnType = method.getReturnType();
    }
    // 判断返回值类型
    this.returnsVoid = void.class.equals(this.returnType);
    this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
    this.returnsCursor = Cursor.class.equals(this.returnType);
    this.returnsOptional = Optional.class.equals(this.returnType);
    // 处理返回值是MAP的情况
    this.mapKey = getMapKey(method);
    this.returnsMap = this.mapKey != null;
    // 解析参数中RowBounds对象的位置
    this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
    // 解析参数中ResultHandler对象的位置
    this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    // 参数名称解析
    this.paramNameResolver = new ParamNameResolver(configuration, method);
}

ParamNameResolver

  • 处理方法的参数名称
/**
 * 参数名解析器的构造方法
 *
 * @param config 配置信息
 * @param method 要被分析的方法
 */
public ParamNameResolver(Configuration config, Method method) {
    // 获取方法参数类型列表
    final Class<?>[] paramTypes = method.getParameterTypes();
    // 准备存储所有的参数的注解,是二维数组,分别存储参数顺序与参数注解(0,Param),(1,Param)
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    // 循环处理各个参数
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 跳过RowBounds与ResultHandler参数
        if (isSpecialParameter(paramTypes[paramIndex])) {
            // skip special parameters
            // 跳过特别参数
            continue;
        }
        // 参数名称
        String name = null;
        for (Annotation annotation : paramAnnotations[paramIndex]) {
            // 找出参数的注解@Param
            if (annotation instanceof Param) {
                // 如果注解是 Param
                hasParamAnnotation = true;
                // 那就以Param中的值作为参数名
                name = ((Param) annotation).value();
                break;
            }
        }
        if (name == null) {
            // 如果没有@Param注解,并且在配置文件中配置了useActualParamName=true,则使用参数实际的名称
            // 但为了使用该特性,必须采用Java8编译,并且编译时需要加上-parameters选项,否则实际参数名称形如arg0
            // @Param was not specified.
            if (config.isUseActualParamName()) {
                name = getActualParamName(method, paramIndex);
            }
            if (name == null) {
                // use the parameter index as the name ("0", "1", ...)
                // gcode issue #71
                // 参数名称取不到,则按照参数的index命名
                name = String.valueOf(map.size());
            }
        }
        // 参数顺序:参数名称
        map.put(paramIndex, name);
    }
    // 锁定map,不可修改
    names = Collections.unmodifiableSortedMap(map);
}

MapperMethod#execute

/**
 * 执行映射接口中的方法
 *
 *  MapperMethod类将一个数据库操作语句和一个 Java方法绑定在了一起:它的MethodSignature属性保存了这个方法的详细信息;
 *  它的 SqlCommand属性持有这个方法对应的 SQL语句。因而只要调用 MapperMethod对象的 execute方法,就可以触发具体的数据库操作,于是数据库操作就被转化为了方法
 *
 * @param sqlSession sqlSession接口的实例,通过它可以进行数据库的操作
 * @param args       执行接口方法时传入的参数
 * @return
 */
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // 根据SQL语句类型,执行不同的操作
        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()) { // 返回返回为void,且有结果处理器
                // 使用结果处理器执行查询
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) { // 多条结果查询
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) { // map结果查询
                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()) {
        // 查询结果为null,但返回类型为基本类型。因此返回变量无法接收查询结果,抛出异常。
        throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    // 返回执行结果
    return result;
}

以上便是Mybatis中Mapper方法执行流程。