温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Mybatis SqlSession执行流程介绍

发布时间:2021-07-12 09:10:11 来源:亿速云 阅读:100 作者:chen 栏目:开发技术

这篇文章主要讲解了“Mybatis SqlSession执行流程介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Mybatis SqlSession执行流程介绍”吧!

目录
  • Mybatis执行SQL流程

    • SqlSession

    • Executor

  • Mybatis之Executor

    • Mybatis之StatementHandler

      • 进入ResultSetHandler

    Mybatis执行SQL流程

    在看源码之前,我们需要了解一些基本知识,如果您没有阅读Mybatis SqlSessionFactory 初始化原理,可以先阅读Mybatis SqlSessionFactory 初始化原理这篇文章,这用更有助于我们理解接下来的文章
    在看源码之前,我们需要了解一些基本知识

    SqlSession

    SqlSession是一个接口,它有两个实现类:
     - DefaultSqlSession:默认实现类
     - SqlSessionManager:已经弃用的实现类,所以我们不需要关注他
    SqlSession是与数据库交互的顶层类,通常与ThreadLocal绑定,一个会话使用一个SqlSession,SqlSession是线程不安全的,使用完毕需要close()

    public class DefaultSqlSession implements SqlSession {
     private final Configuration configuration;
     private final Executor executor;
    }

    SqlSession中最重要的两个变量:
     - Configuration:核心配置类,也是初始化时传过来的
     - Executor:实际执行SQL的执行器

    Executor

    Executor是一个接口,有三个实现类
     - BatchExecutor 重用语句,并执行批量更新
     - ReuseExecutor 重用预处理语句prepared statements
     - SimpleExecutor 普通的执行器,默认使用

    了解完基本知识后,我们接着往下看代码。

    当创建完SqlSessionFactory后,就可以创建SqlSession,然后使用SqlSession进行增删改查:

    // 1. 读取配置文件,读成字节输入流,注意:现在还没解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析配置文件,封装Configuration对象   创建DefaultSqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> objects = sqlSession.selectList("namespace.id");

    我们先去看openSession()方法,创建了SqlSession

    //6. 进入openSession方法
    @Override
    public SqlSession openSession() {
        //getDefaultExecutorType()传递的是SimpleExecutor
        // level:数据库事物级别,null
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    
    //7. 进入openSessionFromDataSource。
    //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
    //openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // 获得 Environment 对象
            final Environment environment = configuration.getEnvironment();
            // 创建 Transaction 对象
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建 Executor 对象
            final Executor executor = configuration.newExecutor(tx, execType);
            // 创建 DefaultSqlSession 对象
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // 如果发生异常,则关闭 Transaction 对象
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    通过源码可以清晰的看到,会话工厂创建了Environment,Transaction,Executor,DefaultSqlSession对象,并且对于会话对象来说,他的autoCommit默认为false,默认不自动提交。

    然后我回到原来的代码,接着就需要使用SqlSession进行增删改查操作了

    所以我们进入selectList()查看

    //8.进入selectList方法,多个重载方法
    @Override
    public <E> List<E> selectList(String statement) {
        return this.selectList(statement, null);
    }
    
    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 获得 MappedStatement 对象
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 执行查询
            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();
        }
    }

    selectList有多个重载方法,进入到最终方法后,我们可以看到它做了两件事

    • 通过statementId,从Configuration中取MappedStatement对象,就是存放了sql语句,返回值类型,输入值类型的对象

    • 然后委派Executor执行器去执行具体的增删改查方法

    所以,对于实际JDBC的操作,我们还需要进入Executor中查看

    Mybatis之Executor

    我们继续从刚刚selectList源码中,进入Executor查看

    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    //此方法在SimpleExecutor的父类BaseExecutor中实现
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
        BoundSql boundSql = ms.getBoundSql(parameter);
        //为本次查询创建缓存的Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // 查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    拆分成了三大步:

    (1)先调用MappedStatement的getBoundSql方法,获取解析后的SQL语句,解析工作是由SqlSourceBuilder完成的

    什么叫解析后的SQL语句呢?因为Mybatis编写SQL语句时,会用到动态SQL,比如#{}占位符,这种占位符JDBC是不认识的,所以需要将其转换成?占位符,并且将其内部的字段名存储起来,后面填充参数的时候好使用反射获取值。

    /**
     * 执行解析原始 SQL ,成为 SqlSource 对象
     *
     * @param originalSql 原始 SQL
     * @param parameterType 参数类型
     * @param additionalParameters 附加参数集合。可能是空集合,也可能是 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合
     * @return SqlSource 对象
     */
    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        // 创建 ParameterMappingTokenHandler 对象
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        // 创建 GenericTokenParser 对象
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        // 执行解析
        String sql = parser.parse(originalSql);
        // 创建 StaticSqlSource 对象
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }

    上面代码就可以看到,会将拆分#{和},进行解析

    (2)根据查询条件,创建缓存key,用来接下来去缓存查找是否有已经执行过的结果

    (3)调用重载query()方法

    接着我们进入重载方法查看:

    @Override
        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());
            // 已经关闭,则抛出 ExecutorException 异常
            if (closed) {
                throw new ExecutorException("Executor was closed.");
            }
            // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
            if (queryStack == 0 && ms.isFlushCacheRequired()) {
                clearLocalCache();
            }
            List<E> list;
            try {
                // queryStack + 1
                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 - 1
                queryStack--;
            }
            if (queryStack == 0) {
                // 执行延迟加载
                for (DeferredLoad deferredLoad : deferredLoads) {
                    deferredLoad.load();
                }
                // issue #601
                // 清空 deferredLoads
                deferredLoads.clear();
                // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
                if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    // issue #482
                    clearLocalCache();
                }
            }
            return list;
        }

    主要的逻辑:

    • 从一级缓存取数据,如果有直接使用缓存的进行接下来的操作

    • 如果没有,从数据库查询

    进入queryFromDatabase()方法:

    // 从数据库中读取操作
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 执行读操作
            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;
    }
    
    @Override
    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();
            // 传入参数创建StatementHanlder对象来执行查询
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 创建jdbc中的statement对象
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 执行 StatementHandler  ,进行读操作
            return handler.query(stmt, resultHandler);
        } finally {
            // 关闭 StatementHandler 对象
            closeStatement(stmt);
        }
    }

    通过代码可以看到,对于实际与JDBC交互的代码,Executor也懒得搞,又像SqlSession一样,委派给小弟StatementHandler了。

    Mybatis之StatementHandler

    我们从刚刚的Executor的代码查看

    @Override
    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();
            // 传入参数创建StatementHanlder对象来执行查询
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 创建jdbc中的statement对象
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 执行 StatementHandler  ,进行读操作
            return handler.query(stmt, resultHandler);
        } finally {
            // 关闭 StatementHandler 对象
            closeStatement(stmt);
        }
    }

    可以看到,这里创建完StatementHandler后,回调用prepareStatement()方法,用来创建Statement对象

    我们进入prepareStatement方法中查看

    // 初始化 StatementHandler 对象
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 获得 Connection 对象
        Connection connection = getConnection(statementLog);
        // 创建 Statement 或 PrepareStatement 对象
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
        handler.parameterize(stmt);
        return stmt;
    }
    
    @Override
    public void parameterize(Statement statement) throws SQLException {
        //使用ParameterHandler对象来完成对Statement的设值
        parameterHandler.setParameters((PreparedStatement) statement);
    }

    这里可以看到,它实际是使用ParameterHandler来设置Statement的参数

    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 遍历 ParameterMapping 数组
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                // 获得 ParameterMapping 对象
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    // 获得值
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    // 获得 typeHandler、jdbcType 属性
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    // 设置 ? 占位符的参数
                    try {
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException | SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
    }

    这段代码的主要目的,就是获取入参,然后根据值,来设置?占位符的参数

    TypeHandler是具体进行参数设置的对象

    所以handler.prepare(connection, transaction.getTimeout());方法,就是使用ParameterHandler来对占位符位置的参数进行值设置

    然后我们回到Executor,查看handler.query()方法

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        // 执行查询
        ps.execute();
        // 处理返回结果
        return resultSetHandler.handleResultSets(ps);
    }

    代码很简单,这里直接使用JDBC的PreparedStatement来进行SQL执行,然后使用ResultSetHandler进行结果数据封装处理。

    进入ResultSetHandler

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
        // 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        // 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);
    
        // 获得 ResultMap 数组
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount); // 校验
        while (rsw != null && resultMapCount > resultSetCount) {
            // 获得 ResultMap 对象
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 处理 ResultSet ,将结果添加到 multipleResults 中
            handleResultSet(rsw, resultMap, multipleResults, null);
            // 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
            rsw = getNextResultSet(stmt);
            // 清理
            cleanUpAfterHandlingResultSet();
            // resultSetCount ++
            resultSetCount++;
        }
    
        // 因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可
        // ···
    
        // 如果是 multipleResults 单元素,则取首元素返回
        return collapseSingleResultList(multipleResults);
    }
    
     // 处理 ResultSet ,将结果添加到 multipleResults 中
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
        try {
            // 暂时忽略,因为只有存储过程的情况,调用该方法,parentMapping 为非空
            if (parentMapping != null) {
                handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
            } else {
                // 如果没有自定义的 resultHandler ,则创建默认的 DefaultResultHandler 对象
                if (resultHandler == null) {
                    // 创建 DefaultResultHandler 对象
                    DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                    // 处理 ResultSet 返回的每一行 Row
                    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                    // 添加 defaultResultHandler 的处理的结果,到 multipleResults 中
                    multipleResults.add(defaultResultHandler.getResultList());
                } else {
                    // 处理 ResultSet 返回的每一行 Row
                    handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
                }
            }
        } finally {
            // issue #228 (close resultsets)
            // 关闭 ResultSet 对象
            closeResultSet(rsw.getResultSet());
        }
    }

    代码比较多,实际最重要的代码就是

    // 添加 defaultResultHandler 的处理的结果,到 multipleResults 中
    multipleResults.add(defaultResultHandler.getResultList());

    将处理后的结果封装到集合中返回,这样基本Mybatis逻辑就走完了.

    我们来回顾一下,都用到了哪些类

    Mybatis SqlSession执行流程介绍

    简单总结
    SqlSessionFactoryBuilder:

    • 解析核心配置文件,创建Configuration

      • XMLConfigBuilder.parse():解析核心配置文件

      • XMLMapperBuilder.parse():解析映射配置文件MappedStatement

    • 创建SqlSessionFactory,默认创建DefaultSqlSessionFactory

    SqlSessionFactory:

    • openSession():构建Executor,SqlSession等

    SqlSession:

    • 根据statementId获取MappedStatement

    • 委派给Executor执行器执行

    Executor:

    • 使用SqlSourceBuilder,将SQL解析成JDBC认可的

    • 查询缓存,是否存在结果

    • 结果不存在,委派给StatementHandler处理器

    StatementHandler:

    • PreparedStatement:处理参数,将参数赋值到占位符上

      • TypeHandler:具体设置值的类

    • ResultSetHandler:封装结果集,封装成设置的返回值类型

      • TypeHandler:根据结果集,取出对应列

    感谢各位的阅读,以上就是“Mybatis SqlSession执行流程介绍”的内容了,经过本文的学习后,相信大家对Mybatis SqlSession执行流程介绍这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI