Mybatis之SQL执行

上文我们简单描述了Mybatis从配置到Sql执行的步骤,本文将从获取SqlSession开始,通过获取Mapper,最终分析Mapper方法的执行流程。我们将借助源码逐步探索这一过程。

在这里插入图片描述

获取SqlSession

// 通过sqlSessionFactory获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 进入openSession()
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 1.获取配置环境
      final Environment environment = configuration.getEnvironment();
      // 2.获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 3.创建新的事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 4.获取执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 5.并返回默认的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
...
  }

数据源信息

1-3步骤数据源配置信息都是之前加载全局文件mybatis-config.xml解析好放入了configuration对象中,如下图:
在这里插入图片描述

Excutor执行器

下图是Excutor的简单介绍
在这里插入图片描述

// 下面来看获取执行器的逻辑
// 传入参数tx(事务)+ execType (执行类型,SIMPLE, REUSE, BATCH)
final Executor executor = configuration.newExecutor(tx, execType);
// -> 
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 如果未指定执行器类型,则使用默认执行器类型
    executorType = executorType == null ? defaultExecutorType : executorType;
    // 如果仍未指定执行器类型,则使用简单执行器
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;

    // 根据执行器类型选择创建对应的执行器实例
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    
    // 如果启用了缓存,则将执行器包装为缓存执行器
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    
    // 对执行器应用拦截器链中的所有拦截器
    executor = (Executor) interceptorChain.pluginAll(executor);
    
    // 返回最终的执行器
    return executor;
}
// 我们这边的例子是个简单类型,所以创建的是SimpleExecutor
// 并且cacheEnabled默认是打开的(解析settings节点的时候的默认值)
// 所以这里最终的Executor为CachingExecutor

构建SqlSession

// 最终构建的SqlSession为DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);

获取Mapper

// 通过sqlSession获取UserMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// ->
// 构建sqlSession的时候会有configuration,mapper信息也都提前解析到configuration对象中了
 public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
// ->
// 使用了动态代理来创建UserMapper接口的实现
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取给定类型的 MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    
    // 如果未找到对应的 MapperProxyFactory,抛出异常
    if (mapperProxyFactory == null) {
      throw new BindingException("类型 " + type + " 在 MapperRegistry 中未知。");
    }
    
    try {
      // 创建并返回 Mapper 接口的代理实例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      // 抛出异常,指明获取 Mapper 实例时出错原因
      throw new BindingException("获取 Mapper 实例时出错。原因:" + e, e);
    }
  }

// ->
public T newInstance(SqlSession sqlSession) {
    // 创建一个 MapperProxy 实例,用于代理 Mapper 接口的方法调用
    // sqlSession:用于执行数据库操作的 SqlSession 实例
    // mapperInterface:要代理的 Mapper 接口类型
    // methodCache:缓存 Mapper 方法的信息
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    
    // 通过 MapperProxy 创建并返回 Mapper 接口的代理实例
    return newInstance(mapperProxy);
}
// 见下图返回的是个代理对象

在这里插入图片描述

Mapper方法的执行流程

// userMapper执行方法
User user = userMapper.selectById(1);
// 调用Mapper接口的所有方法都会先调用到MapperProxy这个代理类的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 如果调用的方法是 Object 类的方法,则直接执行
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        // 如果调用的方法是默认方法(default method),则执行默认方法
        else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        // 捕获并重新抛出可能出现的异常,将其转换为运行时异常
        throw ExceptionUtil.unwrapThrowable(t);
    }

    // 根据方法获取相应的 MapperMethod,然后执行该方法
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}

先来看 cachedMapperMethod(method);

final MapperMethod mapperMethod = cachedMapperMethod(method);
// ->
// 这里构造了MapperMethod对象,里面包括执行的 SQL 命令、参数映射、结果处理等,以及其他与数据库操作相关的配置。这些信息使得 MyBatis 能够在执行 Mapper 接口方法时,根据配置来构造和执行对应的 SQL 操作
 private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

再来看mapperMethod.execute(sqlSession, args);

return mapperMethod.execute(sqlSession, args);
// -> 
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    
    // 根据 SQL 命令类型进行处理
    switch (command.getType()) {
        // 对于 INSERT 类型的命令
        case INSERT: {
            // 将方法参数转换为对应的 SQL 命令参数
            Object param = method.convertArgsToSqlCommandParam(args);
            // 执行插入操作并获取影响的行数
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        
        // 对于 UPDATE 类型的命令
        case UPDATE: {
            // 将方法参数转换为对应的 SQL 命令参数
            Object param = method.convertArgsToSqlCommandParam(args);
            // 执行更新操作并获取影响的行数
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        
        // 对于 DELETE 类型的命令
        case DELETE: {
            // 将方法参数转换为对应的 SQL 命令参数
            Object param = method.convertArgsToSqlCommandParam(args);
            // 执行删除操作并获取影响的行数
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        
        // 对于 SELECT 类型的命令
        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 结果的情况,执行查询返回 Map
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                // 对于返回 Cursor 结果的情况,执行查询返回 Cursor
                result = executeForCursor(sqlSession, args);
            } else {
                // 对于其他情况,执行查询返回单个结果
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                        && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        
        // 对于 FLUSH 类型的命令
        case FLUSH:
            // 执行缓存刷新操作
            result = sqlSession.flushStatements();
            break;
        
        default:
            // 抛出异常,表示不支持的执行方法
            throw new BindingException("未知的执行方法:" + command.getName());
    }
    
    // 对于返回值为基本类型的情况,不允许返回 null
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper 方法 '" + command.getName()
                + "' 在使用基本类型返回时尝试返回了 null(返回类型:" + method.getReturnType() + ")。");
    }
    
    // 返回执行结果
    return result;
}

现在直接来到我们当前sql的代码块

// args 是传递给代理方法的参数数组;param将被用作SQL命令的参数
// command.getName() 获取了与方法对应的SQL命令的 id(通常对应 XML 配置文件中的 id 属性)
result = sqlSession.selectOne(command.getName(), param);
// ->
// 1.调用 selectList 方法执行查询操作,获取结果列表。
// 2.如果结果列表中有且仅有一个元素,则返回该元素作为查询结果。
// 3.如果结果列表中有多个元素,则抛出 TooManyResultsException 异常,表示查询结果过多。
// 4.如果结果列表为空,则返回 null,表示没有查询到结果。
@Override
public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } 
    else if (list.size() > 1) {
        throw new TooManyResultsException("在 selectOne() 中期望返回一个结果(或 null),但发现了多个结果:" + list.size());
    } 
    else {
        return null;
    }
}
// -> this.selectList(statement, parameter);
// statement:要执行的 SQL 语句的唯一标识(对应 XML 映射文件中的 id 属性)。
// parameter:传递给 SQL 语句的参数。
// rowBounds:用于限制结果集返回的行数
// 主要步骤:
// 1.通过调用 configuration.getMappedStatement 获取被映射的 SQL 语句信息。
// 2.使用 executor 执行查询操作,executor.query 方法接受 SQL 语句信息、参数、行数限制和结果处理器等参数,返回结果列表。
// 3.在执行过程中,如果出现异常,将异常重新包装为运行时异常,抛出并指明错误原因。
// 4.无论是否发生异常,最后会重置 MyBatis 的错误上下文。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("查询数据库时出错。原因:" + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}
// 执行器执行executor.query(..)
// 又上文知道这里执行器被包装过,所有最外层是CachingExecutor
// ->
// ms:被映射的 SQL 语句信息,包含了 SQL 语句、参数映射等信息。
// parameter:传递给 SQL 语句的参数。
// rowBounds:用于限制结果集返回的行数。
// resultHandler:用于处理查询结果的结果处理器。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql 主要包含以下信息:
    // 1.sql:这是包含占位符的原始的SQL语句
    // 2.parameterMappings:包含了参数映射信息的列表
    // 3.parameterObject:传递给SQL语句的参数对象
    // 4.additionalParameters:额外的参数映射
    // 5.metaParameters:用于访问和操作 Java 对象属性的工具类
    BoundSql boundSql = ms.getBoundSql(parameter);
    
    // 创建用于缓存的 CacheKey
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    
    // 调用 query 方法执行查询操作,并返回结果列表
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

进入查询操作query(…)

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {
    // 获取 MappedStatement 对应的缓存
    Cache cache = ms.getCache();
    
    // 如果存在缓存
    if (cache != null) {
        // 根据需要刷新缓存
        flushCacheIfRequired(ms);
        
        // 如果 MappedStatement 配置了使用缓存,并且没有结果处理器
        if (ms.isUseCache() && resultHandler == null) {
            // 确保没有输出参数
            ensureNoOutParams(ms, boundSql);
            
            // 尝试从缓存中获取结果列表
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            
            // 如果缓存中没有结果列表
            if (list == null) {
                // 执行查询操作获取结果列表
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                
                // 将结果列表放入缓存中(issue #578 and #116)
                tcm.putObject(cache, key, list);
            }
            return list;
        }
    }
    
    // 如果不满足缓存条件,直接执行查询操作
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

第一次缓存肯定没有值,所以直接查询delegate.query(…)

// 一直往下跟,直接来到查询代码
// ->
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// -> 
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
// -> 
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
// -> doQuery(...)
// 综合来看,这段代码负责执行 SQL 查询操作,并将查询结果交给结果处理器进行处理,最终返回处理后的结果列表。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        // 获取 MappedStatement 的配置信息
        Configuration configuration = ms.getConfiguration();
        
        // 创建 StatementHandler 对象,用于处理 SQL 语句
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        
        // 准备 Statement 对象,并记录日志
        stmt = prepareStatement(handler, ms.getStatementLog());
        
        // 调用 StatementHandler 的 query 方法执行查询操作
        return handler.query(stmt, resultHandler);
    } finally {
        // 在 finally 中关闭 Statement 对象
        closeStatement(stmt);
    }
}

总结

Mybatis的代码还是比较清晰的,跟着一步一步下来,都能摸清大致的脉络,捋起袖子加油干~~
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值