第五章-Mybatis源码解析-执行流程(一)

本文详细解析了Mybatis的执行流程,包括SqlSession、Mapper接口、MappedStatement、BoundSql、CacheKey等核心组件的工作原理,以及SimpleExecutor和CachingExecutor执行器在查询过程中的作用。
摘要由CSDN通过智能技术生成

第五章-Mybatis源码解析-执行流程

一切准备就绪,现在开始执行了。一般执行有两种方式:一是直接通过SqlSession中的方法执行;二是通过sqlSession.getMapper(),然后拿到Mapper再通过mapper的接口方法间接执行,如下示例:

SqlSession sqlSession = xmlStart();
Blog blog = sqlSession.selectOne("polyphagic.code.mybatis.BlogMapper.selectBlog1", 1);
System.out.println(blog);

BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
blog = mapper.selectBlog1(1);
System.out.println("sqlSession.getMapper 方式:" + blog);

5.1 SqlSession方法直接执行

由于SqlSession中定义的方法较多,这里不会每个都讲,只抽取能代表CRUD的4个方法来讲。

5.1.1 selectOne

Mybatis默认提供的SqlSession实现类是DefaultSqlSession,那直接进入这个类

/**
statement:对应的mapper id,如上文,就是polyphagic.code.mybatis.BlogMapper.selectBlog1
parameter:要传的参数,如上文,此处为1
*/
public <T> T selectOne(String statement, Object parameter) {
    // 最终都是返回List
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) { // 只有一个值,只接取出
        return list.get(0);
    } else if (list.size() > 1) { // 超出一个值就有问题,抛出异常,因为入口方法是只查一个
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}

public <E> List<E> selectList(String statement, Object parameter) {
    // RowBounds.DEFAULT 设置行边界对象,这是为了按指定偏移量和界限取数据,就好比分页
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 取出先前解析出来的 MappedStatement 对象,解析过程可以看章节`3.2.2.6`
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 再走到执行器中去执行,继续往里走。这里先介绍一下wrapCollection方法,看关联1
        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();
    }
}

// 关联1
private Object wrapCollection(final Object object) {
     if (object instanceof Collection) {
         StrictMap<Object> map = new StrictMap<>();
         // 如果是集合类,就以 key = collection 存放
         map.put("collection", object);
         if (object instanceof List) {
             // 如果是 List,就再以 key = list 存放,所以当我们在xml中配置时,传参是list的时候,取 collection 或 list 都可以
             map.put("list", object);
         }
         return map;
     } else if (object != null && object.getClass().isArray()) {
         StrictMap<Object> map = new StrictMap<>();
         // 如果是数组,就以 key = array 存放
         map.put("array", object);
         return map;
     }
     return object;
 }

进入执行器之前,先看看执行器的继承关系图吧,图是网上找(偷个懒,不想画)

图5-1
在这里插入图片描述

SimpleExecutor:一般不带二级缓存的时候用

CachingExecutor:带二级缓存时用

这里只讲上面两个执行器的源码,其他两个执行器,由读者自行扩展。

5.1.1.1 SimpleExecutor执行器

接着上面的源码继续往里走,此时会进到 BaseExecutor.query 方法

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 重点是要取出sql,在mybatis中,sql都用BoundSql表示,继续往下看,看看到底怎么取
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建 CacheKey,下文也有讲解
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 继续查询,看关联1
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

// 关联1 的实现
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) { // 执行器状态判断
        throw new ExecutorException("Executor was closed.");
    }
    // 当在一个会话中,首次进来时,并且mapper语句设置flushCache="true"时,才会清除localCache,就是一级缓存了,涉及一二级缓存的,到时单独抽一章来讲,这里暂且知道就行
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        // 先从一级缓存中取,一级缓存只作用于同一个SqlSession
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) { // 缓存中有值
            // 处理存储过程的逻辑,这个由读者自行扩展
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else { // 缓存中没值,那就从数据库中去查,看关联2 
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--; // 恢复为0
    }
    if (queryStack == 0) {
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        // 判断一级缓存范围,默认是session会话级,如果是STATEMENT语句级,那么语句执行就要清除,实际上,这种情况就是没有缓存了
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

// 关联2 
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 {
        // 从数据库中取到值,具体实现看章节`5.1.1.1-4`
        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;
}

走到 MappedStatement.getBoundSql 方法

public BoundSql getBoundSql(Object parameterObject) {
    // 这里分别涉及到从 RawSqlSource【里面装饰了 StaticSqlSource(静态sql)】和 DynamicSqlSource(动态sql)中取,关于这两种sql形式,在前面章节`3.2.2.6`
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 拿到所有的参数映射,ParameterMapping 在章节`3.2.2.6`的addMappedStatement 方法中生成
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // 啥参数都不需要的sql,就用下面的方式再重新创建一个
    if (parameterMappings == null || parameterMappings.isEmpty()) {
        boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    // 这个只有在 parameterMap 中使用,但是 parameterMap 已被废弃,就不做讲解,看源码描述,也是为了解决 (issue #30) 这个bug
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
        String rmId = pm.getResultMapId();
        if (rmId != null) {
            ResultMap rm = configuration.getResultMap(rmId);
            if (rm != null) {
                hasNestedResultMaps |= rm.hasNestedResultMaps();
            }
        }
    }

    return boundSql;
}
1.RawSqlSource.getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
    // 这里的sqlSource是被 RawSqlSource 装饰的,实际上就是 StaticSqlSource,继续下探
    return sqlSource.getBoundSql(parameterObject);
}

// StaticSqlSource 类
public BoundSql getBoundSql(Object parameterObject) {
    // 创建一个 BoundSql,BoundSql 构建函数中主要就是赋值操作
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

// BoundSql 类,截取部分代码
public class BoundSql {

    private final String sql;
    private final List<ParameterMapping> parameterMappings;
    private final Object parameterObject;
    private final Map<String, Object> additionalParameters;
    private final MetaObject metaParameters;

    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap<>();
        this.metaParameters = configuration.newMetaObject(additionalParameters);
    }
}
2.DynamicSqlSource.getBoundSql
3.创建CacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) { // 验证执行器状态
        throw new ExecutorException("Executor was closed.");
    }
    // 通过以下各对象值的hashcode 组装成 CacheKey,有兴趣的读者可以进去看看update内部实现
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    // 拿出 ParameterMapping
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // 取出 类型处理器
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // 遍历 parameterMappings
    for (ParameterMapping parameterMapping : parameterMappings) {
        // 判断不是输出
        if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            // 属性名,就是#{}内部包裹的名字
            String propertyName = parameterMapping.getProperty();
            // 额外参数,这块只有 DynamicSqlSource 时才有
            if (boundSql.hasAdditionalParameter(propertyName)) {
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                // parameterObject 就是调用时实际传过来的参数值,如果为null,那value自然也就是null
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                // 如果有自带的类型处理器,就直接赋值给value
                value = parameterObject;
            } else {
                // 自定义类型,那就要从对象中取出对应的属性值,并赋值给 value
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
        }
    }
    if (configuration.getEnvironment() != null) {
        // issue #176
        cacheKey.update(configuration.getEnvironment().getId());
    }
    // 最终得到一个 cacheKey
    return cacheKey;
}
4 doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        // 获取 Configuration 对象
        Configuration configuration = ms.getConfiguration();
        // 拿到 StatementHandler,这个对象就是真正跟jdbc打交道的,封装了jdbc对数据的操作,详情解析看章节`第六章`,以下执行流程都放在`第六章`讲解
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 预处理
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 执行查询操作,并且会做好结果处理,结果处理将放在`第七章`讲解
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值