剖析Mybatis的SQL执行过程

SQL 执行过程分析

今天又来一点硬核的东西,我们一起来看看SQL的执行过程

为 Mapper 接口创建代理对象

先看看调用栈:剖析Mybatis的SQL执行过程_第1张图片

// 本质:
//  MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
     
    /*
     * 创建 MapperProxy 对象,MapperProxy 实现了 
     * InvocationHandler 接口,代理逻辑封装在此类中
     */
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
// 


protected T newInstance(MapperProxy<T> mapperProxy) {
     
    // 通过 JDK 动态代理创建代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{
     mapperInterface}, mapperProxy);
}

这里创建的MapperProxy 对象,我们可以简单看一下

剖析Mybatis的SQL执行过程_第2张图片
下面就可以调用接口方法进行数据库操作,由于接口方法会被代理逻辑拦截,所以下面我们把目光聚焦在代理逻辑上面,看看代理逻辑会做哪些事情。

执行代理逻辑

在执行SQL之前,先看看 创建 MapperMethod 对象

public class MapperMethod {
     

    private final SqlCommand command;
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
     
        // 创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
        this.command = new SqlCommand(config, mapperInterface, method);
        // 创建 MethodSignature 对象,从类名中可知,该对象包含了被拦截方法的一些信息
        this.method = new MethodSignature(config, mapperInterface, method);
        // 这两个对象分别记录了不同的信息,这些信息在后续的方法调用中都会被用到。
    }
}
执行 execute 方法
// MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
     
    Object result;
    
    // 根据 SQL 类型执行相应的数据库操作
    switch (command.getType()) {
     
        case INSERT: {
     
            // 对用户传入的参数进行转换,下同
            Object param = method.convertArgsToSqlCommandParam(args);
            // 执行插入操作,rowCountResult 方法用于处理返回值
            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,但参数列表中包含 ResultHandler,表明使用者
                 * 想通过 ResultHandler 的方式获取查询结果,而非通过返回值获取结果
                 */
                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()) {
     
                // 执行查询操作,并返回一个 Cursor 对象
                result = executeForCursor(sqlSession, args);
            } else {
     
                Object param = method.convertArgsToSqlCommandParam(args);
                // 执行查询操作,并返回一个结果
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        case FLUSH:
            // 执行刷新操作
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    
    // 如果方法的返回值为基本类型,而返回值却为 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;
}

从上面一大串swtich语句可以发现MyBatis 对哪些 SQL 指令提供了支持,如下:

  • 查询语句:SELECT
  • 更新语句:INSERT/UPDATE/DELETE
  • 存储过程:CALL

在上面的列表中,我刻意对 SELECT/INSERT/UPDATE/DELETE 等指令进行了分类,分类依据指令的功能以及 MyBatis 执行这些指令的过程。这里把 SELECT 称为查询语句,INSERT/UPDATE/DELETE 等称为更新语句。接下来,先来分析查询语句的执行过程。

查询语句的执行过程分析

selectOne 方法分析

​ 这里分析selectOne 方法是为了告知大家,selectOneselectList 方法是有联系的,同时分析 selectOne 方法等同于分析 selectList 方法。如果你不信的话,那我们看源码吧,源码面前了无秘密。

剖析Mybatis的SQL执行过程_第3张图片

看看最后一个 selectList 方法

private final Executor executor;

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
     
    try {
     
        // 获取 MappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 调用 Executor 实现类中的 query 方法
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
     
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
     
        ErrorContext.instance().reset();
    }
}

重点看看Executor

剖析Mybatis的SQL执行过程_第4张图片

默认情况下,executor 的类型为 CachingExecutor,该类是一个装饰器类,用于给目标 Executor 增加二级缓存功能。那目标 Executor 是谁呢?默认情况下是 SimpleExecutor。我们可以看一下一开始的SqlSession sqlSession2 = factory.openSession(true);可以看看调用栈:image-20200716151155274

剖析Mybatis的SQL执行过程_第5张图片

因为这里,我开启了二级缓存,所以会进入CachingExecutor 的 query 方法

// -☆- CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
     
    // 获取 BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建 CacheKey
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 调用重载方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

先跳过BoundSql和CacheKey,先看看query方法

// -☆- CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     
    // 从 MappedStatement 中获取缓存
    Cache cache = ms.getCache();
    // 若映射文件中未配置缓存或参照缓存,此时 cache = null
    if (cache != null) {
     
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
     
            ensureNoOutParams(ms, boundSql);
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
     
                // 若缓存未命中,则调用被装饰类的 query 方法,也就是BaseExecutor的 query 方法
                list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    // 调用被装饰类的 query 方法
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

// 下面来看一下 BaseExecutor 的中签名相同的 query 方法是如何实现的
// -☆- BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     
    if (closed) {
     
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
     
        clearLocalCache();
    }
    List<E> list;
    try {
     
        queryStack++;
        // 从一级缓存中获取缓存项
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
     
            // 存储过程相关处理逻辑,本文不分析存储过程,故该方法不分析了
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
     
            // 一级缓存未命中,则从数据库中查询
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
     
        queryStack--;
    }
    if (queryStack == 0) {
     
        // 从一级缓存中延迟加载嵌套查询结果,这个还没搞懂,迟点再说
        for (DeferredLoad deferredLoad : deferredLoads) {
     
            deferredLoad.load();
        }
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
     
            clearLocalCache();
        }
    }
    return list;
}

// -☆- BaseExecutor
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
    ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     
    List<E> list;
    // 向缓存中存储一个占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
     
        // 调用 doQuery 进行查询
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
     
        // 移除占位符
        localCache.removeObject(key);
    }
    // 缓存查询结果
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
     
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

// -☆- SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     
    Statement stmt = null;
    try {
     
        Configuration configuration = ms.getConfiguration();
        // 创建 StatementHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 创建 Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 执行查询操作
        return handler.<E>query(stmt, resultHandler);
    } finally {
     
        // 关闭 Statement
        closeStatement(stmt);
    }
}

// 先看看上面的query方法
// -☆- PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行 SQL
    ps.execute();
    // 处理执行结果
    return resultSetHandler.<E>handleResultSets(ps);
}


// RoutingStatementHandler 路由器
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     
    return delegate.<E>query(statement, resultHandler);
  }

// -☆- PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行 SQL
    ps.execute();
    // 处理执行结果
    return resultSetHandler.<E>handleResultSets(ps);
}

TM的,终于要看到头了。最后看看这个调用栈剖析Mybatis的SQL执行过程_第6张图片

获取 BoundSql

​ 我们在执行 SQL 时,一个重要的任务是将 SQL 语句解析出来。我们都知道 SQL 是配置在映射文件中的,但由于映射文件中的 SQL 可能会包含占位符 #{},以及动态 SQL 标签,比如 、 等。因此,我们并不能直接使用映射文件中配置的 SQL。MyBatis 会将映射文件中的 SQL 解析成一组 SQL 片段。如果某个片段中也包含动态 SQL 相关的标签,那么,MyBatis 会对该片段再次进行分片。最终,一个 SQL 配置将会被解析成一个 SQL 片段树

下面我们来看一下 BoundSql 类的成员变量信息,如下:

// 一个完整的 SQL 语句,可能会包含问号 ? 占位符
private String sql;
// 参数映射列表,SQL 中的每个 #{xxx} 占位符都会被解析成相应的 ParameterMapping 对象
private List<ParameterMapping> parameterMappings;
//运行时参数,即用户传入的参数,比如 Article 对象,或是其他的参数
private Object parameterObject;
// 附加参数集合,用于存储一些额外的信息,比如 datebaseId 等
private Map<String, Object> additionalParameters;
// // 附加参数集合,用于存储一些额外的信息,比如 datebaseId 等
private MetaObject metaParameters;

接下来,开始分析 BoundSql 的构建过程。我们源码之旅的第一站是 MappedStatement 的 getBoundSql 方法

// -☆- MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
     

    // 调用 sqlSource 的 getBoundSql 获取 BoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
     
        /*
         * 创建新的 BoundSql,这里的 parametx`erMap 是 ParameterMap 类型。
         * 由 节点进行配置,该节点已经废弃,不推荐使用。默认情况下,
         * parameterMap.getParameterMappings() 返回空集合
         */ 
        boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // 省略不重要的逻辑

    return boundSql;
}

MappedStatement 的 getBoundSql 在内部调用了 SqlSource 实现类的 getBoundSql 方法,这里看看SqlSource 实现类

剖析Mybatis的SQL执行过程_第7张图片

// -☆- DynamicSqlSource
public BoundSql getBoundSql(Object parameterObject) {
     
    // 创建 DynamicContext
    DynamicContext context = new DynamicContext(configuration, parameterObject);

    // 解析 SQL 片段,并将解析结果存储到 DynamicContext 中
    rootSqlNode.apply(context);
    
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    /*
     * 构建 StaticSqlSource,在此过程中将 sql 语句中的占位符 #{} 替换为问号 ?,
     * 并为每个占位符构建相应的 ParameterMapping
     */
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    
    // 调用 StaticSqlSource 的 getBoundSql 获取 BoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

    // 将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql 中
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
     
        boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
}

// 下面逐步分析上面的步骤
1. 
public class DynamicContext {
     

    public static final String PARAMETER_OBJECT_KEY = "_parameter";
    public static final String DATABASE_ID_KEY = "_databaseId";

    private final ContextMap bindings;
    private final StringBuilder sqlBuilder = new StringBuilder();

    public DynamicContext(Configuration configuration, Object parameterObject) {
     
        // 创建 ContextMap
        if (parameterObject != null && !(parameterObject instanceof Map)) {
     
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            bindings = new ContextMap(metaObject);
        } else {
     
            bindings = new ContextMap(null);
        }

        // 存放运行时参数 parameterObject 以及 databaseId
        bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
        bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
    }

    // 省略部分代码
}

​ 其中 sqlBuilder 变量用于存放 SQL 片段的解析结果,bindings 则用于存储一些额外的信息,比如运行时参数 和 databaseId 等。bindings 类型为 ContextMap,ContextMap 定义在 DynamicContext 中,是一个静态内部类。剖析Mybatis的SQL执行过程_第8张图片

解析 SQL 片段

对于一个包含了 ${} 占位符,或 、 等标签的 SQL,在解析的过程中,会被分解成多个片段。每个片段都有对应的类型,每种类型的片段都有不同的解析逻辑。在源码中,片段这个概念等价于 sql 节点,即 SqlNode。SqlNode 是一个接口,它有众多的实现类。其继承体系如下:剖析Mybatis的SQL执行过程_第9张图片

在众多实现类中,StaticTextSqlNode 用于存储静态文本,TextSqlNode 用于存储带有 ${} 占位符的文本,IfSqlNode 则用于存储 节点的内容。MixedSqlNode 内部维护了一个 SqlNode 集合,用于存储各种各样的 SqlNode。

解析 #{} 占位符

与 ${} 占位符的处理方式不同,MyBatis 并不会直接将 #{} 占位符替换为相应的参数值。

入口:上面那个方法的第三步剖析Mybatis的SQL执行过程_第10张图片

// -☆- SqlSourceBuilder
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
     
    // 创建 #{} 占位符处理器
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 创建 #{} 占位符解析器
    // GenericTokenParser 是一个通用的标记解析器,用于解析形如 ${xxx},#{xxx} 等标记
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 解析 #{} 占位符,并返回解析结果
    String sql = parser.parse(originalSql);
    // 封装解析结果到 StaticSqlSource 中,并返回
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

// 重点看看这个 #{} 占位符处理器ParameterMappingTokenHandler 
public String handleToken(String content) {
     
    // 获取 content 的对应的 ParameterMapping
    parameterMappings.add(buildParameterMapping(content));
    // 返回 ?
    return "?";
}

// GenericTokenParser 负责将 #{} 占位符中的内容抽取出来,并将抽取出的内容传给 handleToken 方法。handleToken 负责将传入的参数解析成对应的 ParameterMapping 对象,这步操作由 buildParameterMapping 方法完成。
private ParameterMapping buildParameterMapping(String content) {
     
    /*
     * 将 #{xxx} 占位符中的内容解析成 Map。大家可能很好奇一个普通的字符串是怎么解析成 Map 的,
     * 举例说明一下。如下:
     * 
     *    #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
     *    
     * 上面占位符中的内容最终会被解析成如下的结果:
     * 
     *  {
     *      "property": "age",
     *      "typeHandler": "MyTypeHandler", 
     *      "jdbcType": "NUMERIC", 
     *      "javaType": "int"
     *  }
     * 
     * parseParameterMapping 内部依赖 ParameterExpression 对字符串进行解析,ParameterExpression 的
     * 逻辑不是很复杂,这里就不分析了。大家若有兴趣,可自行分析
     */
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    // metaParameters 为 DynamicContext 成员变量 bindings 的元信息对象
    if (metaParameters.hasGetter(property)) {
     
        propertyType = metaParameters.getGetterType(property);
    
    /*
     * parameterType 是运行时参数的类型。如果用户传入的是单个参数,比如 Article 对象,此时 
     * parameterType 为 Article.class。如果用户传入的多个参数,比如 [id = 1, author = "coolblog"],
     * MyBatis 会使用 ParamMap 封装这些参数,此时 parameterType 为 ParamMap.class。如果 
     * parameterType 有相应的 TypeHandler,这里则把 parameterType 设为 propertyType
     */
    } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
     
        propertyType = parameterType;
    } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
     
        propertyType = java.sql.ResultSet.class;
    } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
     
        // 如果 property 为空,或 parameterType 是 Map 类型,则将 propertyType 设为 Object.class
        propertyType = Object.class;
    } else {
     
        /*
         * 代码逻辑走到此分支中,表明 parameterType 是一个自定义的类,
         * 比如 Article,此时为该类创建一个元信息对象
         */
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        // 检测参数对象有没有与 property 想对应的 getter 方法
        if (metaClass.hasGetter(property)) {
     
            // 获取成员变量的类型
            propertyType = metaClass.getGetterType(property);
        } else {
     
            propertyType = Object.class;
        }
    }
    
    // -------------------------- 分割线 ---------------------------
    
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    
    // 将 propertyType 赋值给 javaType
    Class<?> javaType = propertyType;
    String typeHandlerAlias = null;
    
    // 遍历 propertiesMap
    for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
     
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
     
            // 如果用户明确配置了 javaType,则以用户的配置为准
            javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
     
            // 解析 jdbcType
            builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
     ...} 
        else if ("numericScale".equals(name)) {
     ...} 
        else if ("resultMap".equals(name)) {
     ...} 
        else if ("typeHandler".equals(name)) {
     
        	typeHandlerAlias = value;    
        } 
        else if ("jdbcTypeName".equals(name)) {
     ...} 
        else if ("property".equals(name)) {
     ...} 
        else if ("expression".equals(name)) {
     
            throw new BuilderException("Expression based parameters are not supported yet");
        } else {
     
            throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content
                + "}.  Valid properties are " + parameterProperties);
        }
    }
    if (typeHandlerAlias != null) {
     
        // 解析 TypeHandler
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
    }
    
    // 构建 ParameterMapping 对象
    return builder.build();
}

如上,buildParameterMapping 代码很多,逻辑看起来很复杂。但是它做的事情却不是很多,只有3件事情。如下:

  1. 解析 content
  2. 解析 propertyType,对应分割线之上的代码
  3. 构建 ParameterMapping 对象,对应分割线之下的代码

上面说的这个获取BoundSql,最后就是要这么一个结果剖析Mybatis的SQL执行过程_第11张图片

创建 StatementHandler

​ 在 MyBatis 的源码中,StatementHandler 是一个非常核心接口。之所以说它核心,是因为从代码分层的角度来说,StatementHandler 是 MyBatis 源码的边界,再往下层就是 JDBC 层面的接口了。StatementHandler 需要和 JDBC 层面的接口打交道,它要做的事情有很多。在执行 SQL 之前,StatementHandler 需要创建合适的 Statement 对象,然后填充参数值到 Statement 对象中,最后通过 Statement 对象执行 SQL。这还不算完,待 SQL 执行完毕,还要去处理查询结果等。这些过程看似简单,但实现起来却很复杂。下面我们来看一下 StatementHandler 的继承体系。剖析Mybatis的SQL执行过程_第12张图片

上图中,最下层的三种 StatementHandler 实现类与三种不同的 Statement 进行交互,这个不难看出来。但 RoutingStatementHandler 则是一个奇怪的存在,因为 JDBC 中并不存在 RoutingStatement。那它有什么用呢?其实在上篇文章已经简单解析过了,接下来,我们再重点看看

先看看 RoutingStatementHandler的创建的入口方法在哪剖析Mybatis的SQL执行过程_第13张图片

// -☆- Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
    Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
     
    // 创建具有路由功能的 StatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 应用插件到 StatementHandler 上
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

// 下面分析一下 RoutingStatementHandler
public class RoutingStatementHandler implements StatementHandler {
     

    private final StatementHandler delegate;

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
        ResultHandler resultHandler, BoundSql boundSql) {
     

        // 根据 StatementType 创建不同的 StatementHandler 
        switch (ms.getStatementType()) {
     
            case STATEMENT:
                delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
    }
    
    // 其他方法逻辑均由别的 StatementHandler 代理完成,就不贴代码了
}

RoutingStatementHandler 的构造方法会根据 MappedStatement 中的 statementType 变量创建不同的 StatementHandler 实现类。默认情况下,statementType 值为 PREPARED。剖析Mybatis的SQL执行过程_第14张图片

设置运行时参数到 SQL 中

JDBC 提供了三种 Statement 接口,分别是 Statement、PreparedStatement 和 CallableStatement。他们的关系如下:剖析Mybatis的SQL执行过程_第15张图片

​ 上面三个接口的层级分明,其中 Statement 接口提供了执行 SQL,获取执行结果等基本功能。PreparedStatement 在此基础上,对 IN 类型的参数提供了支持。使得我们可以使用运行时参数替换 SQL 中的问号 ? 占位符,而不用手动拼接 SQL。CallableStatement 则是 在 PreparedStatement 基础上,对 OUT 类型的参数提供了支持,该种类型的参数用于保存存储过程输出的结果。

Statement 的创建入口是在SimpleExecutor 的 prepareStatement 方法中剖析Mybatis的SQL执行过程_第16张图片

// -☆- SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
     
    Statement stmt;
    // 获取数据库连接,这里并没有直接调用JDBC的getConnection吗,而是通过数据源获取获取连接
	// MyBatis 提供了两种基于 JDBC 接口的数据源,分别为 PooledDataSource 和 UnpooledDataSource。创建或获取数据库连接的操作最终是由这两个数据源执行
    Connection connection = getConnection(statementLog);
    // 创建 Statement,
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 为 Statement 设置 IN 参数
    handler.parameterize(stmt);
    return stmt;
}

// 这个数据源问题留给下次再讲
 protected Connection getConnection(Log statementLog) throws SQLException {
     
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
     
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
     
      return connection;
    }
  }

// 分析 PreparedStatement 的创建,以及 IN 参数设置的过程
// -☆- PreparedStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
     
    Statement statement = null;
    try {
     
        // 创建 Statement
        statement = instantiateStatement(connection);
        // 设置超时和 FetchSize
        setStatementTimeout(statement, transactionTimeout);
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
     
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
     
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}

protected Statement instantiateStatement(Connection connection) throws SQLException {
     
    String sql = boundSql.getSql();
    // 根据条件调用不同的 prepareStatement 方法创建 PreparedStatement
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
     
        String[] keyColumnNames = mappedStatement.getKeyColumns();
        if (keyColumnNames == null) {
     
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        } else {
     
            return connection.prepareStatement(sql, keyColumnNames);
        }
    } else if (mappedStatement.getResultSetType() != null) {
     
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
     
        return connection.prepareStatement(sql);
    }
}

// 分析运行时参数是如何被设置到 SQL 中的过程。
// -☆- PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
     
    // 通过参数处理器 ParameterHandler 设置运行时参数到 PreparedStatement 中
    parameterHandler.setParameters((PreparedStatement) statement);
}

public class DefaultParameterHandler implements ParameterHandler {
     
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

    public void setParameters(PreparedStatement ps) {
     
        /*
         * 从 BoundSql 中获取 ParameterMapping 列表,每个 ParameterMapping 
         * 与原始 SQL 中的 #{xxx} 占位符一一对应
         */
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
     
            for (int i = 0; i < parameterMappings.size(); i++) {
     
                ParameterMapping parameterMapping = parameterMappings.get(i);
                // 检测参数类型,排除掉 mode 为 OUT 类型的 parameterMapping
                if (parameterMapping.getMode() != ParameterMode.OUT) {
     
                    Object value;
                    // 获取属性名
                    String propertyName = parameterMapping.getProperty();
                    // 检测 BoundSql 的 additionalParameters 是否包含 propertyName
                    if (boundSql.hasAdditionalParameter(propertyName)) {
     
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
     
                        value = null;

                    // 检测运行时参数是否有相应的类型解析器
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
     
                        /*
                         * 若运行时参数的类型有相应的类型处理器 TypeHandler,则将 
                         * parameterObject 设为当前属性的值。
                         */
                        value = parameterObject;
                    } else {
     
                        // 为用户传入的参数 parameterObject 创建元信息对象
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        // 从用户传入的参数中获取 propertyName 对应的值
                        value = metaObject.getValue(propertyName);
                    }
                    
                    // ---------------------分割线---------------------

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
     
                        // 此处 jdbcType = JdbcType.OTHER
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
     
                        // 由类型处理器 typeHandler 向 ParameterHandler 设置参数
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException e) {
     
                        throw new TypeException(...);
                    } catch (SQLException e) {
     
                        throw new TypeException(...);
                    }
                }
            }
        }
    }
}

分割线以上的大段代码用于获取 #{xxx} 占位符属性所对应的运行时参数。分割线以下的代码则是获取 #{xxx} 占位符属性对应的 TypeHandler,并在最后通过 TypeHandler 将运行时参数值设置到 PreparedStatement 中

总结:#{} 占位符的解析与参数的设置过程梳理

SELECT * FROM author WHERE name = #{name} AND age = #{age}在运行时这两个占位符会被解析成两个 ParameterMapping 对象:ParameterMapping{property='name', mode=IN, javaType=class java.lang.String, jdbcType=null, ...}ParameterMapping{property='age', mode=IN, javaType=class java.lang.Integer, jdbcType=null, ...}

#{} 占位符解析完毕后,得到的 SQL: SELECT * FROM Author WHERE name = ? AND age = ?

这里假设下面这个方法与上面的 SQL 对应:Author findByNameAndAge(@Param("name") String name, @Param("age") Integer age)

该方法的参数列表会被 ParamNameResolver 解析成一个 map,如下:

{
     
    0: "name",
    1: "age"
}

假设该方法在运行时有如下的调用:findByNameAndAge("professor", 20)

此时,需要再次借助 ParamNameResolver 力量。这次我们将参数名和运行时的参数值绑定起来,得到如下的映射关系。

{
     
    "name": "professor",
    "age": 20,
    "param1": "professor",
    "param2": 20
}

下一步,我们要将运行时参数设置到 SQL 中。由于原 SQL 经过解析后,占位符信息已经被擦除掉了,我们无法直接将运行时参数 SQL 中。不过好在,这些占位符信息被记录在了 ParameterMapping 中了,MyBatis 会将 ParameterMapping 会按照 #{} 的解析顺序存入到 List 中。这样我们通过 ParameterMapping 在列表中的位置确定它与 SQL 中的哪个 ? 占位符相关联。同时通过 ParameterMapping 中的 property 字段,我们到“参数名与参数值”映射表中查找具体的参数值。这样,我们就可以将参数值准确的设置到 SQL 中了,此时 SQL :SELECT * FROM Author WHERE name = "professor" AND age = 20

当运行时参数被设置到 SQL 中 后,下一步要做的事情是执行 SQL,然后处理 SQL 执行结果。对于更新操作,数据库一般返回一个 int 行数值,表示受影响行数,这个处理起来比较简单。但对于查询操作,返回的结果类型多变,处理方式也很复杂。接下来,我们就来看看 MyBatis 是如何处理查询结果的。

这一章节暂时还没彻底弄懂,这里强行分析了,就先留着吧!!!以后再来复盘搞一下

处理查询结果

MyBatis 可以将查询结果,即结果集 ResultSet 自动映射成实体类对象。这样使用者就无需再手动操作结果集,并将数据填充到实体类对象中。 MyBatis 中,结果集的处理工作由结果集处理器 ResultSetHandler 执行。ResultSetHandler 是一个接口,它只有一个实现类 DefaultResultSetHandler。结果集的处理入口方法是 handleResultSets,下面来看一下该方法的实现

public List<Object> handleResultSets(Statement stmt) throws SQLException {
     
    
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    // 获取第一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);

    while (rsw != null && resultMapCount > resultSetCount) {
     
        ResultMap resultMap = resultMaps.get(resultSetCount);
        // 处理结果集
        handleResultSet(rsw, resultMap, multipleResults, null);
        // 获取下一个结果集
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
    }

    // 以下逻辑均与多结果集有关,就不分析了,代码省略
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
     ...}

    return collapseSingleResultList(multipleResults);
}

private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
     
    // 获取结果集
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
     
        /*
         * 移动 ResultSet 指针到下一个上,有些数据库驱动可能需要使用者
         * 先调用 getMoreResults 方法,然后才能调用 getResultSet 方法
         * 获取到第一个 ResultSet
         */
        if (stmt.getMoreResults()) {
     
            rs = stmt.getResultSet();
        } else {
     
            if (stmt.getUpdateCount() == -1) {
     
                break;
            }
        }
    }
    /*
     * 这里并不直接返回 ResultSet,而是将其封装到 ResultSetWrapper 中。
     * ResultSetWrapper 中包含了 ResultSet 一些元信息,比如列名称、每列对应的 JdbcType、
     * 以及每列对应的 Java 类名(class name,譬如 java.lang.String)等。
     */
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}

// 我们把目光聚焦在单结果集的处理逻辑上
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
     
    try {
     
        if (parentMapping != null) {
     
            // 多结果集相关逻辑,不分析了
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
     
            /*
             * 检测 resultHandler 是否为空。ResultHandler 是一个接口,使用者可实现该接口,
             * 这样我们可以通过 ResultHandler 自定义接收查询结果的动作。比如我们可将结果存储到
             * List、Map 亦或是 Set,甚至丢弃,这完全取决于大家的实现逻辑。
             */ 
            if (resultHandler == null) {
     
                // 创建默认的结果处理器
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                // 处理结果集的行数据
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
     
                // 处理结果集的行数据
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } finally {
     
        closeResultSet(rsw.getResultSet());
    }
}

// 用于处理结果集中的数据
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
        RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
     

    if (resultMap.hasNestedResultMaps()) {
     
        ensureNoRowBounds();
        checkResultHandler();
        // 处理嵌套映射,关于嵌套映射本文就不分析了
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
     
        // 处理简单映射
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

//handleRowValues 方法中针对两种映射方式进行了处理。一种是嵌套映射,另一种是简单映射。本文所说的嵌套查询是指  中嵌套了一个  ,关于此种映射的处理方式本文就不进行分析了。下面我将详细分析简单映射的处理逻辑,如下:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
        ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
     

    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    // 根据 RowBounds 定位到指定行记录
    skipRows(rsw.getResultSet(), rowBounds);
    // 检测是否还有更多行的数据需要处理
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
     
        // 获取经过鉴别器处理后的 ResultMap
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
        // 从 resultSet 中获取结果
        Object rowValue = getRowValue(rsw, discriminatedResultMap);
        // 存储结果
        storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
}

上面方法的逻辑较多,这里简单总结一下。如下:

  1. 根据 RowBounds 定位到指定行记录
  2. 循环处理多行数据
  3. 使用鉴别器处理 ResultMap
  4. 映射 ResultSet,得到映射结果 rowValue
  5. 存储结果

分析一下第四步 ResultSet 的映射过程

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
     
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 创建实体类对象,比如 Article 对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
     
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        
        // 检测是否应该自动映射结果集
        if (shouldApplyAutomaticMappings(resultMap, false)) {
     
            // 进行自动映射
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
        }
        // 根据  节点中配置的映射关系进行映射
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

// 创建实体类对象
// -☆- DefaultResultSetHandler
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
     

    this.useConstructorMappings = false;
    final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
    final List<Object> constructorArgs = new ArrayList<Object>();

    // 调用重载方法创建实体类对象
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    // 检测实体类是否有相应的类型处理器
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
     
        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {
     
            // 如果开启了延迟加载,则为 resultObject 生成代理类
            if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
     
                /*
                 * 创建代理类,默认使用 Javassist 框架生成代理类。由于实体类通常不会实现接口,
                 * 所以不能使用 JDK 动态代理 API 为实体类生成代理。
                 */
                resultObject = configuration.getProxyFactory()
                    .createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
                break;
            }
        }
    }
    this.useConstructorMappings =
        resultObject != null && !constructorArgTypes.isEmpty();
    return resultObject;
}

// 重点分析一下这个重载方法
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException {
     

    final Class<?> resultType = resultMap.getType();
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    // 获取  节点对应的 ResultMapping
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();

    /*
     * 检测是否有与返回值类型相对应的 TypeHandler,若有则直接从
     * 通过 TypeHandler 从结果集中提取数据,并生成返回值对象
     */
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
     
        // 通过 TypeHandler 获取提取,并生成返回值对象
        return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
     
        /*
         * 通过  节点配置的映射信息从 ResultSet 中提取数据,
         * 然后将这些数据传给指定构造方法,即可创建实体类对象
         */
        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
     
        // 通过 ObjectFactory 调用目标类的默认构造方法创建实例
        return objectFactory.create(resultType);
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
     
        // 通过自动映射查找合适的构造方法创建实例
        return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

// 接下里要做的事情是将结果集中的数据映射到实体类对象中
// 在 MyBatis 中,结果集自动映射有三种等级。三种等级官方文档上有所说明,这里直接引用一下。如下:

// NONE - 禁用自动映射。仅设置手动映射属性
// PARTIAL - 将自动映射结果除了那些有内部定义内嵌结果映射的(joins)
// FULL - 自动映射所有

// 看一下自动映射相关的逻辑
private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
     
    // 检测  是否配置了 autoMapping 属性
    if (resultMap.getAutoMapping() != null) {
     
        // 返回 autoMapping 属性
        return resultMap.getAutoMapping();
    } else {
     
        if (isNested) {
     
            // 对于嵌套 resultMap,仅当全局的映射行为为 FULL 时,才进行自动映射
            return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
        } else {
     
            // 对于普通的 resultMap,只要全局的映射行为不为 NONE,即可进行自动映射
            return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
        }
    }
}


// 如何自动映射
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
     

    // 获取 UnMappedColumnAutoMapping 列表
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
     
        for (UnMappedColumnAutoMapping mapping : autoMapping) {
     
            // 通过 TypeHandler 从结果集中获取指定列的数据
            final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
            if (value != null) {
     
                foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
     
                // 通过元信息对象设置 value 到实体类对象的指定字段上
                metaObject.setValue(mapping.property, value);
            }
        }
    }
    return foundValues;
}

// UnMappedColumnAutoMapping 用于记录未配置在  节点中的映射关系。该类定义在 DefaultResultSetHandler 内部
private static class UnMappedColumnAutoMapping {
     

    private final String column;
    private final String property;
    private final TypeHandler<?> typeHandler;
    private final boolean primitive;

    public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
     
        this.column = column;
        this.property = property;
        this.typeHandler = typeHandler;
        this.primitive = primitive;
    }
}

// 获取 UnMappedColumnAutoMapping 集合的过程
// -☆- DefaultResultSetHandler
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
     

    final String mapKey = resultMap.getId() + ":" + columnPrefix;
    // 从缓存中获取 UnMappedColumnAutoMapping 列表
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    // 缓存未命中
    if (autoMapping == null) {
     
        autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
        // 从 ResultSetWrapper 中获取未配置在  中的列名
        final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
        for (String columnName : unmappedColumnNames) {
     
            String propertyName = columnName;
            if (columnPrefix != null && !columnPrefix.isEmpty()) {
     
                if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
     
                    // 获取不包含列名前缀的属性名
                    propertyName = columnName.substring(columnPrefix.length());
                } else {
     
                    continue;
                }
            }
            // 将下划线形式的列名转成驼峰式,比如 AUTHOR_NAME -> authorName
            final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
            if (property != null && metaObject.hasSetter(property)) {
     
                // 检测当前属性是否存在于 resultMap 中
                if (resultMap.getMappedProperties().contains(property)) {
     
                    continue;
                }
                // 获取属性对于的类型
                final Class<?> propertyType = metaObject.getSetterType(property);
                if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
     
                    // 获取类型处理器
                    final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
                    // 封装上面获取到的信息到 UnMappedColumnAutoMapping 对象中
                    autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
                } else {
     
                    configuration.getAutoMappingUnknownColumnBehavior()
                        .doAction(mappedStatement, columnName, property, propertyType);
                }
            } else {
     
                /*
                 * 若 property 为空,或实体类中无 property 属性,此时无法完成
                 * 列名与实体类属性建立映射关系。针对这种情况,有三种处理方式,
                 *   1. 什么都不做
                 *   2. 仅打印日志
                 *   3. 抛出异常
                 * 默认情况下,是什么都不做
                 */
                configuration.getAutoMappingUnknownColumnBehavior()
                    .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
            }
        }
        // 写入缓存
        autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
}

剖析Mybatis的SQL执行过程_第17张图片

关联查询与延迟加载

延迟加载和关联查询怎么用我就不说了, 因为网上有很多关于这种的教程,你也可以直接从我gitee上clone下来,直接调试,这里就重点提一下他和普通查询有什么不同。

下面就来看看延迟查询的结果剖析Mybatis的SQL执行过程_第18张图片

剖析Mybatis的SQL执行过程_第19张图片

源码就暂时不分析了,因为还没有理解透彻!!!

更新语句的执行过程分析

​ 执行更新语句所需处理的情况较之查询语句要简单不少,两者最大的区别更新语句的执行结果类型单一,处理逻辑要简单不是。除此之外,两者在缓存的处理上也有比较大的区别。更新过程会立即刷新缓存,而查询过程则不会。

更新语句执行过程全貌

​ 首先,我们还是从 MapperMethod 的 execute 方法开始看起

// -☆-  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:
            // ...
            break;
        case FLUSH:
            // ...
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
     ...}
    return result;
}
// 如上,插入、更新以及删除操作最终都调用了 SqlSession 接口中的方法。这三个方法返回值均是受影响行数,是一个整型值。

// 继续往下走一步
// 我们发现最后调用的都是update方法!!!
// -☆- DefaultSqlSession
public int insert(String statement, Object parameter) {
     
    return update(statement, parameter);
}

public int delete(String statement, Object parameter) {
     
    return update(statement, parameter);
}

public int update(String statement, Object parameter) {
     
    try {
     
        dirty = true;
        // 获取 MappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 调用 Executor 的 update 方法
        return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
     
        throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
     
        ErrorContext.instance().reset();
    }
}

// 下面分析 Executor 的 update 方法
// -☆- CachingExecutor
// 默认情况下,insert、update 和 delete 操作都会清空一二级缓存。
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
     
    // 刷新二级缓存
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
}

// -☆- BaseExecutor
public int update(MappedStatement ms, Object parameter) throws SQLException {
     
    if (closed) {
     
        throw new ExecutorException("Executor was closed.");
    }
    // 刷新一级缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
}

// 下面分析 doUpdate 方法,该方法是一个抽象方法,因此我们到 BaseExecutor 的子类 SimpleExecutor 中看看该方法是如何实现的
// -☆- SimpleExecutor
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
     
    Statement stmt = null;
    try {
     
        Configuration configuration = ms.getConfiguration();
        // 创建 StatementHandler
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
        // 创建 Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 调用 StatementHandler 的 update 方法
        return handler.update(stmt);
    } finally {
     
        closeStatement(stmt);
    }
}

// 下面分析 PreparedStatementHandler 的 update 方法
// -☆- PreparedStatementHandler
public int update(Statement statement) throws SQLException {
     
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行 SQL
    ps.execute();
    // 返回受影响行数
    int rows = ps.getUpdateCount();
    // 获取用户传入的参数值,参数值类型可能是普通的实体类,也可能是 Map
    Object parameterObject = boundSql.getParameterObject();
    
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    // 获取自增主键的值,并将值填入到参数对象中
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
}

处理更新结果

更新语句的执行结果是一个整型值,表示本次更新所影响的行数。由于返回值类型简单,因此处理逻辑也很简单。

// -☆-  MapperMethod
private Object rowCountResult(int rowCount) {
     
    final Object result;

    /*
     * 这里的 method 类型为 MethodSignature,即方法签名,包含了某个方法较为详细的信息。
     */
    if (method.returnsVoid()) {
     
        // 方法返回类型为 void,则不用返回结果,这里将结果置空
        result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
     
        // 方法返回类型为 Integer 或 int,直接赋值返回即可
        // 一般是返回这个
        result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
     
        // 如果返回值类型为 Long 或者 long,这里强转一下即可
        result = (long) rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
     
        // 方法返回类型为布尔类型,若 rowCount > 0,则返回 ture,否则返回 false
        result = rowCount > 0;
    } else {
     
        throw new BindingException(...);
    }
    return result;
}

总结

这里总结就引用一下别人博客的总结了,因为我觉得总结得比较好的!!剖析Mybatis的SQL执行过程_第20张图片

在 MyBatis 中,SQL 执行过程的实现代码是有层次的,每层都有相应的功能。比如,SqlSession 是对外接口的接口,因此它提供了各种语义清晰的方法,供使用者调用。Executor 层做的事情较多,比如一二级缓存功能就是嵌入在该层内的。StatementHandler 层主要是与 JDBC 层面的接口打交道。至于 ParameterHandler 和 ResultSetHandler,一个负责向 SQL 中设置运行时参数,另一个负责处理 SQL 执行结果,它们俩可以看做是 StatementHandler 辅助类。最后看一下右边横跨数层的类,Configuration 是一个全局配置类,很多地方都依赖它。MappedStatement 对应 SQL 配置,包含了 SQL 配置的相关信息。BoundSql 中包含了已完成解析的 SQL 语句,以及运行时参数等。

from 田小波的博客

个人唠叨

好了,这期的个人唠叨又来了,这期还是继续讲回上期的内容,还没有看过的朋友,建议先看看我上期写的这篇博客先!

每一低谷背后,其实都藏着自律这一条出路

暑假第二天了,不知道你还有没有继续保持着学习,经历了期末的考验之后,很多人都会想歇一歇,但是真的需要歇一歇吗?我觉得根本没必要,如果你真的需要歇一歇,只不过是你的思想在作怪,因为理所当然地,考完试当然是要放松放松啦!!不然后面怎么有精力去学习呢?哈哈哈哈,你是不是每年寒暑假都有这种想法,一有这种想法,你的整个寒暑假很有可能就这样荒废了,反正我每次这样想,我的寒暑假都是颓废的一个寒暑假!
那我们究竟要怎么才可以破掉怎么个魔咒呢?身边很多朋友都夸我自律,其实只有我自己知道,那是我在当时的处境中,唯一能够做的选择。我不想坐以待毙,渴望拯救自己,却也在无形中改变了自己。因为我渴望,所以我有这么一个决心要改变自己,改变自己的本质还是要自律,关于我一天的自律生活在这篇文章的文末有提到,没看过的朋友可以去看看,那怎么才可以有渴望呢?那就是要有目标,我之前的文章页多次强调目标的重要性,**因为有了目标你才有努力的方向,你没有目标,你再怎么努力也是没用的!!**所以,这个暑假希望你们能找到自己的一个目标,关于怎么找到目标,我在这篇文章也有提到过,感兴趣的朋友可以再回顾回顾。找到目标之后,很自然地你就会往这个方向而不断地努力,在努力的过程中,你很自然地就会养成一种自律的习惯,这个是必然的!!举个例子:我挺喜欢打篮球的,因为他可以让我释放掉好多的压力,有了这个释放压力的目标之后,所以你每天都会很自觉地去篮球场打球,如果有一天下雨,你不能去打球,你会发现你浑身上下都不舒服,哈哈哈哈,所以,我还是会在家锻炼锻炼,因为不出汗真的很不舒服!

​ 好了,这里主要提到了目标的重要性,这里再来举一个例子来说说目标到底有多重要

有一个目标到底有多重要

因为最近都看到朋友们沉浸在这个操作系统的考试里面,所以就特意在这里说一下吧,其实上一篇的个人唠叨也已经说过了,主要是这几天要出成绩了,感觉大家应该多多少少都会有焦虑的,那为什么会有焦虑呢?先说说我的这次考试吧,毫无意外,我这次考试也挂科了!!嗯,确实是自己太菜了!昨晚无意中看到了成绩,平时秒睡的我竟然也会在床上躺了差不多半个小时才能睡着,那我为什么会焦虑呢?本质就是:过于在意别人对自己评价,通过别人的评价来肯定来否定自己!嗯,确实是这样,挂科并不可怕,挂多少科都不可怕,重要是你可以从中学到什么。而我却在担心,挂科了,会不会被别人看扁了,会不会在别人眼里,我就没有那么牛逼了!其实,现在的我还是没能完全不看别人对自己的评价,但是我有在努力,总有一天我会做到的!!
回到正题,这次的挂科,对我来说更多的是一种塞翁失马焉知非福的感觉,因为我的目标是要进大厂,所以,这次挂科到底可以对我实现这个目标能带来什么呢?我们都知道,操作系统是四大金刚之一,面试必问,如果你连这么简单的期末考都搞不定,更别说进大厂了,能不能找到工作都是一个问题,所以,目标到底有多重要,重要就体现在这里,当你遇到某个挫折的时候,你随之也会产生焦虑感,那这种焦虑带给你的仅仅只是焦虑吗?如果你有了一个目标就不一样了,你可以通过这个目标来分析这个挫折到底真正带给你什么,如果确实带给你一些东西,那你应该开心才对,而且会为之而付出,所以慢慢地,这种焦虑感就会消失,那这次挫折并没有带给你什么,那你也无需焦虑,你焦虑这不过是别人的眼光!!所以,最后提出一个本质:只有你不断努力才会消除你的焦虑感,因为焦虑感每个人每时每刻都会有,不妨多努力一点,就少焦虑一点!

最后有一句想分享给大家:
认真的时候工作,糊涂的时候看书
独处的时候思考,难过的时候睡觉

希望大家尽快从这个期末考后焦虑中走出来,尽快找到自己的目标,并为之而付出,暑假第二天,共勉!!!

你可能感兴趣的:(Mybatis源码解析,java,orm,数据库,mybatis)