剖析Mybatis的SQL执行过程

SQL 执行过程分析

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

为 Mapper 接口创建代理对象

先看看调用栈:image-20200716143531097

// 本质:
//  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 对象,我们可以简单看一下

image-20200716143852515
下面就可以调用接口方法进行数据库操作,由于接口方法会被代理逻辑拦截,所以下面我们把目光聚焦在代理逻辑上面,看看代理逻辑会做哪些事情。

执行代理逻辑

在执行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 方法。如果你不信的话,那我们看源码吧,源码面前了无秘密。

image-20200716145956343

看看最后一个 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

img

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

image-20200716151220192

因为这里,我开启了二级缓存,所以会进入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的,终于要看到头了。最后看看这个调用栈image-20200716154004446

获取 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> 节点进行配置,该节点已经废弃,不推荐使用。默认情况下,
         * parameterMap.getParameterMappings() 返回空集合
         */ 
        boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // 省略不重要的逻辑

    return boundSql;
}

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

image-20200716160857723

// -☆- 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 中,是一个静态内部类。image-20200716163745166

解析 SQL 片段

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

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

解析 #{} 占位符

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

入口:上面那个方法的第三步image-20200716165017495

// -☆- 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,最后就是要这么一个结果image-20200716170436466

创建 StatementHandler

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

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

先看看 RoutingStatementHandler的创建的入口方法在哪image-20200716173149239

// -☆- 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。image-20200716173433791

设置运行时参数到 SQL 中

JDBC 提供了三种 Statement 接口,分别是 Statement、PreparedStatement 和 CallableStatement。他们的关系如下:img

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

Statement 的创建入口是在SimpleExecutor 的 prepareStatement 方法中image-20200716191030211

// -☆- 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 方法中针对两种映射方式进行了处理。一种是嵌套映射,另一种是简单映射。本文所说的嵌套查询是指 <ResultMap> 中嵌套了一个 <ResultMap> ,关于此种映射的处理方式本文就不进行分析了。下面我将详细分析简单映射的处理逻辑,如下:
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;
        }
        // 根据 <resultMap> 节点中配置的映射关系进行映射
        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);
    // 获取 <constructor> 节点对应的 ResultMapping
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();

    /*
     * 检测是否有与返回值类型相对应的 TypeHandler,若有则直接从
     * 通过 TypeHandler 从结果集中提取数据,并生成返回值对象
     */
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
        // 通过 TypeHandler 获取提取,并生成返回值对象
        return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
        /*
         * 通过 <constructor> 节点配置的映射信息从 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) {
    // 检测 <resultMap> 是否配置了 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 用于记录未配置在 <resultMap> 节点中的映射关系。该类定义在 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 中获取未配置在 <resultMap> 中的列名
        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;
}

image-20200716211905006

关联查询与延迟加载

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

下面就来看看延迟查询的结果image-20200717094652981

image-20200717094724861

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

更新语句的执行过程分析

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

更新语句执行过程全貌

​ 首先,我们还是从 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;
}

总结

这里总结就引用一下别人博客的总结了,因为我觉得总结得比较好的!!img

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

from 田小波的博客

个人唠叨

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

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

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

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

有一个目标到底有多重要

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值