This is the 24th day of my participation in the August Text Challenge.More challenges in August

Sequence diagram

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>

The detailed steps

Client

QueryCondition condition = new QueryCondition();
condition.setId(1);
condition.setCategory(1);
// Call the mapper method to execute the query
List<Purchase> purchaseList = mapper.findByCondition(condition);
Copy the code

PurchaseMapper#findByCondition

/** ** select */
List<Purchase> findByCondition(QueryCondition condition);
Copy the code

MapperProxy#invoke

/** * proxy method *@paramProxy Proxy object *@paramMethod Proxy method *@paramParameter * to the args proxy method@returnMethod execution result *@throws Throwable
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) { // A method inherited from Object
            // Execute the old method directly
            return method.invoke(this, args);
        } else if (method.isDefault()) { // The default method
            // Execute the default method
            returninvokeDefaultMethod(proxy, method, args); }}catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    /** * 1. Use method as the key and cache MapperMethod * 2. Return value */ if method key exists
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // Call the execute method in MapperMethod, perform the operation, get the result and return it
    return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
    If there is a key, return the corresponding MapperMethod. If there is no MapperMethod, create a new MapperMethod
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
Copy the code

MapperMethod

/** * MapperMethod constructor **@paramMapperInterface Mapping interface *@paramMethod Specifies the method * in the mapping interface@paramConfig Configuration information Configuration */
public MapperMethod(Class
        mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
}
Copy the code

SqlCommand

  • MappedStatement mapping, SqlCommandType fetch, see Mybatis source mappers tag parsing
public SqlCommand(Configuration configuration, Class
        mapperInterface, Method method) {
    // Method name
    final String methodName = method.getName();
    // The class in which the method resides may be mapperInterface or a subclass of mapperInterface
    finalClass<? > declaringClass = method.getDeclaringClass();/ / get 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 {
        // Get the ID of MappedStatement
        name = ms.getId();
        / / get SqlCommandType
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: "+ name); }}}/** * Find the MappedStatement object * corresponding to the specified method on the specified interface@paramMapperInterface Mapping interface *@paramMethodName specifies the operation methodName * in the mapping interface@paramDeclaringClass Specifies the class in which the action methods are located. Generally, it is the mapping interface itself, or it may be a subclass of the mapping interface *@paramConfiguration Indicates the configuration information *@returnMappedStatement object, which contains a complete database operation statement */
private MappedStatement resolveMappedStatement(Class
        mapperInterface, String methodName, Class
        declaringClass, Configuration configuration) {
    // The number of the database operation statement is: interface name. The method name
    String statementId = mapperInterface.getName() + "." + methodName;
    // Configuration saves all parsed operation statements
    if (configuration.hasStatement(statementId)) {
        // The corresponding statement is found in configuration
        return configuration.getMappedStatement(statementId);
    } else if (mapperInterface.equals(declaringClass)) {
        // Indicates that the recursive call has reached the end point, but still no matching result has been found
        return null;
    }
    // Start with the method's definition class and work up the parent class. Stop when the interface class is found
    for(Class<? > superInterface : mapperInterface.getInterfaces()) {if (declaringClass.isAssignableFrom(superInterface)) {
            MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                    declaringClass, configuration);
            if(ms ! =null) {
                returnms; }}}return null;
}
Copy the code

MethodSignature

public MethodSignature(Configuration configuration, Class
        mapperInterface, Method method) {
    // Get the return value type
    Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
    if (resolvedReturnType instanceofClass<? >) {this.returnType = (Class<? >) resolvedReturnType; }else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<? >) ((ParameterizedType) resolvedReturnType).getRawType(); }else {
        this.returnType = method.getReturnType();
    }
    // Determine the return value type
    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);
    // Handle the case where the return value is MAP
    this.mapKey = getMapKey(method);
    this.returnsMap = this.mapKey ! =null;
    // Resolve the position of the RowBounds object in the argument
    this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
    // Resolve the location of the ResultHandler object in the argument
    this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    // Parameter name resolution
    this.paramNameResolver = new ParamNameResolver(configuration, method);
}
Copy the code

ParamNameResolver

  • Parameter name of the processing method
/** * The argument name parser constructor **@paramConfig Configuration information *@paramMethod The method to be analyzed */
public ParamNameResolver(Configuration config, Method method) {
    // Get a list of method parameter types
    finalClass<? >[] paramTypes = method.getParameterTypes();Param (0,Param); Param (1,Param);
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    // Loop through each argument
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // Skip RowBounds and ResultHandler arguments
        if (isSpecialParameter(paramTypes[paramIndex])) {
            // skip special parameters
            // Skip special arguments
            continue;
        }
        // Parameter name
        String name = null;
        for (Annotation annotation : paramAnnotations[paramIndex]) {
            // Find the annotation @param for the parameter
            if (annotation instanceof Param) {
                // If the annotation is Param
                hasParamAnnotation = true;
                // Take the value in Param as the parameter name
                name = ((Param) annotation).value();
                break; }}if (name == null) {
            // If there is no @param annotation and useActualParamName=true is configured in the configuration file, the actual name of the parameter is used
            In order to use this feature, however, you must compile using Java8 with the -parameters option, otherwise the actual parameter name looks like 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
                // If the parameter name cannot be obtained, use the index name of the parametername = String.valueOf(map.size()); }}// Parameter order: parameter name
        map.put(paramIndex, name);
    }
    // Lock map and cannot be modified
    names = Collections.unmodifiableSortedMap(map);
}
Copy the code

MapperMethod#execute

/** * The MapperMethod class binds a database operation statement to a Java method: its MethodSignature property holds the method details; * Its SqlCommand property holds the SQL statement corresponding to this method. Therefore, by calling the execute method of the MapperMethod object, specific database operations can be triggered, and the database operations are converted to method * *@paramSqlSession Instance of the sqlSession interface through which database operations can be performed *@paramThe argument * passed in when args executes the interface method@return* /
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // Perform different operations depending on the type of SQL statement
        case INSERT: { // If insert statement
            // Match the sequence of arguments to the arguments
            Object param = method.convertArgsToSqlCommandParam(args);
            // Perform the operation and return the result
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: { // If it is an update statement
            // Match the sequence of arguments to the arguments
            Object param = method.convertArgsToSqlCommandParam(args);
            // Perform the operation and return the result
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: { // If delete statement
            // Match the sequence of arguments to the arguments
            Object param = method.convertArgsToSqlCommandParam(args);
            // Perform the operation and return the result
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT: // If it is a query statement
            if (method.returnsVoid() && method.hasResultHandler()) { // Return returns void with result handlers
                // Use the result handler to execute the query
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) { // Multiple result queries
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) { // Map result query
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) { // cursor type result query
                result = executeForCursor(sqlSession, args);
            } else { // Single result query
                // Match the sequence of arguments to the arguments
                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: // Clear the cache
            result = sqlSession.flushStatements();
            break;
        default: // Unknown statement type, throw exception
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null&& method.getReturnType().isPrimitive() && ! method.returnsVoid()) {// The query result is null, but the return type is basic. Therefore, the return variable cannot receive the query result, and an exception is thrown.
        throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    // Returns the execution result
    return result;
}
Copy the code

The above is the Mapper method execution process in Mybatis.