经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MyBatis » 查看文章
浅谈Mybatis SqlSession执行流程
来源:jb51  时间:2021/7/12 9:39:47  对本文有异议

Mybatis执行SQL流程

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

SqlSession

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

  1. public class DefaultSqlSession implements SqlSession {
  2. private final Configuration configuration;
  3. private final Executor executor;
  4. }

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

Executor

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

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

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

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

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

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

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

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

所以我们进入selectList()查看

  1. //8.进入selectList方法,多个重载方法
  2. @Override
  3. public <E> List<E> selectList(String statement) {
  4. return this.selectList(statement, null);
  5. }
  6.  
  7. @Override
  8. public <E> List<E> selectList(String statement, Object parameter) {
  9. return this.selectList(statement, parameter, RowBounds.DEFAULT);
  10. }
  11.  
  12. @Override
  13. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  14. try {
  15. // 获得 MappedStatement 对象
  16. MappedStatement ms = configuration.getMappedStatement(statement);
  17. // 执行查询
  18. return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  19. } catch (Exception e) {
  20. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  21. } finally {
  22. ErrorContext.instance().reset();
  23. }
  24. }

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

  • 通过statementId,从Configuration中取MappedStatement对象,就是存放了sql语句,返回值类型,输入值类型的对象
  • 然后委派Executor执行器去执行具体的增删改查方法

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

Mybatis之Executor

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

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

拆分成了三大步:

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

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

  1. /**
  2. * 执行解析原始 SQL ,成为 SqlSource 对象
  3. *
  4. * @param originalSql 原始 SQL
  5. * @param parameterType 参数类型
  6. * @param additionalParameters 附加参数集合。可能是空集合,也可能是 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合
  7. * @return SqlSource 对象
  8. */
  9. public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  10. // 创建 ParameterMappingTokenHandler 对象
  11. ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  12. // 创建 GenericTokenParser 对象
  13. GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  14. // 执行解析
  15. String sql = parser.parse(originalSql);
  16. // 创建 StaticSqlSource 对象
  17. return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  18. }
  19.  

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

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

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

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

  1. @Override
  2. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  3. ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  4. // 已经关闭,则抛出 ExecutorException 异常
  5. if (closed) {
  6. throw new ExecutorException("Executor was closed.");
  7. }
  8. // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
  9. if (queryStack == 0 && ms.isFlushCacheRequired()) {
  10. clearLocalCache();
  11. }
  12. List<E> list;
  13. try {
  14. // queryStack + 1
  15. queryStack++;
  16. // 从一级缓存中,获取查询结果
  17. list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  18. // 获取到,则进行处理
  19. if (list != null) {
  20. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  21. // 获得不到,则从数据库中查询
  22. } else {
  23. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  24. }
  25. } finally {
  26. // queryStack - 1
  27. queryStack--;
  28. }
  29. if (queryStack == 0) {
  30. // 执行延迟加载
  31. for (DeferredLoad deferredLoad : deferredLoads) {
  32. deferredLoad.load();
  33. }
  34. // issue #601
  35. // 清空 deferredLoads
  36. deferredLoads.clear();
  37. // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
  38. if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  39. // issue #482
  40. clearLocalCache();
  41. }
  42. }
  43. return list;
  44. }
  45.  

主要的逻辑:

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

进入queryFromDatabase()方法:

  1. // 从数据库中读取操作
  2. private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  3. List<E> list;
  4. // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
  5. localCache.putObject(key, EXECUTION_PLACEHOLDER);
  6. try {
  7. // 执行读操作
  8. list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  9. } finally {
  10. // 从缓存中,移除占位对象
  11. localCache.removeObject(key);
  12. }
  13. // 添加到缓存中
  14. localCache.putObject(key, list);
  15. // 暂时忽略,存储过程相关
  16. if (ms.getStatementType() == StatementType.CALLABLE) {
  17. localOutputParameterCache.putObject(key, parameter);
  18. }
  19. return list;
  20. }
  21.  
  22. @Override
  23. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  24. Statement stmt = null;
  25. try {
  26. Configuration configuration = ms.getConfiguration();
  27. // 传入参数创建StatementHanlder对象来执行查询
  28. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  29. // 创建jdbc中的statement对象
  30. stmt = prepareStatement(handler, ms.getStatementLog());
  31. // 执行 StatementHandler ,进行读操作
  32. return handler.query(stmt, resultHandler);
  33. } finally {
  34. // 关闭 StatementHandler 对象
  35. closeStatement(stmt);
  36. }
  37. }

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

Mybatis之StatementHandler

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

  1. @Override
  2. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  3. Statement stmt = null;
  4. try {
  5. Configuration configuration = ms.getConfiguration();
  6. // 传入参数创建StatementHanlder对象来执行查询
  7. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  8. // 创建jdbc中的statement对象
  9. stmt = prepareStatement(handler, ms.getStatementLog());
  10. // 执行 StatementHandler ,进行读操作
  11. return handler.query(stmt, resultHandler);
  12. } finally {
  13. // 关闭 StatementHandler 对象
  14. closeStatement(stmt);
  15. }
  16. }
  17.  

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

我们进入prepareStatement方法中查看

  1. // 初始化 StatementHandler 对象
  2. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  3. Statement stmt;
  4. // 获得 Connection 对象
  5. Connection connection = getConnection(statementLog);
  6. // 创建 Statement 或 PrepareStatement 对象
  7. stmt = handler.prepare(connection, transaction.getTimeout());
  8. // 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
  9. handler.parameterize(stmt);
  10. return stmt;
  11. }
  12.  
  13. @Override
  14. public void parameterize(Statement statement) throws SQLException {
  15. //使用ParameterHandler对象来完成对Statement的设值
  16. parameterHandler.setParameters((PreparedStatement) statement);
  17. }

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

  1. @Override
  2. public void setParameters(PreparedStatement ps) {
  3. ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  4. // 遍历 ParameterMapping 数组
  5. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  6. if (parameterMappings != null) {
  7. for (int i = 0; i < parameterMappings.size(); i++) {
  8. // 获得 ParameterMapping 对象
  9. ParameterMapping parameterMapping = parameterMappings.get(i);
  10. if (parameterMapping.getMode() != ParameterMode.OUT) {
  11. // 获得值
  12. Object value;
  13. String propertyName = parameterMapping.getProperty();
  14. if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
  15. value = boundSql.getAdditionalParameter(propertyName);
  16. } else if (parameterObject == null) {
  17. value = null;
  18. } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
  19. value = parameterObject;
  20. } else {
  21. MetaObject metaObject = configuration.newMetaObject(parameterObject);
  22. value = metaObject.getValue(propertyName);
  23. }
  24. // 获得 typeHandler、jdbcType 属性
  25. TypeHandler typeHandler = parameterMapping.getTypeHandler();
  26. JdbcType jdbcType = parameterMapping.getJdbcType();
  27. if (value == null && jdbcType == null) {
  28. jdbcType = configuration.getJdbcTypeForNull();
  29. }
  30. // 设置 ? 占位符的参数
  31. try {
  32. typeHandler.setParameter(ps, i + 1, value, jdbcType);
  33. } catch (TypeException | SQLException e) {
  34. throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
  35. }
  36. }
  37. }
  38. }
  39. }
  40.  

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

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

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

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

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

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

进入ResultSetHandler

  1. @Override
  2. public List<Object> handleResultSets(Statement stmt) throws SQLException {
  3. ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
  4.  
  5. // 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
  6. // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
  7. final List<Object> multipleResults = new ArrayList<>();
  8.  
  9. int resultSetCount = 0;
  10. // 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
  11. ResultSetWrapper rsw = getFirstResultSet(stmt);
  12.  
  13. // 获得 ResultMap 数组
  14. // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
  15. List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  16. int resultMapCount = resultMaps.size();
  17. validateResultMapsCount(rsw, resultMapCount); // 校验
  18. while (rsw != null && resultMapCount > resultSetCount) {
  19. // 获得 ResultMap 对象
  20. ResultMap resultMap = resultMaps.get(resultSetCount);
  21. // 处理 ResultSet ,将结果添加到 multipleResults 中
  22. handleResultSet(rsw, resultMap, multipleResults, null);
  23. // 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
  24. rsw = getNextResultSet(stmt);
  25. // 清理
  26. cleanUpAfterHandlingResultSet();
  27. // resultSetCount ++
  28. resultSetCount++;
  29. }
  30.  
  31. // 因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可
  32. // ···
  33.  
  34. // 如果是 multipleResults 单元素,则取首元素返回
  35. return collapseSingleResultList(multipleResults);
  36. }
  37.  
  38. // 处理 ResultSet ,将结果添加到 multipleResults 中
  39. private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  40. try {
  41. // 暂时忽略,因为只有存储过程的情况,调用该方法,parentMapping 为非空
  42. if (parentMapping != null) {
  43. handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
  44. } else {
  45. // 如果没有自定义的 resultHandler ,则创建默认的 DefaultResultHandler 对象
  46. if (resultHandler == null) {
  47. // 创建 DefaultResultHandler 对象
  48. DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
  49. // 处理 ResultSet 返回的每一行 Row
  50. handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
  51. // 添加 defaultResultHandler 的处理的结果,到 multipleResults 中
  52. multipleResults.add(defaultResultHandler.getResultList());
  53. } else {
  54. // 处理 ResultSet 返回的每一行 Row
  55. handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
  56. }
  57. }
  58. } finally {
  59. // issue #228 (close resultsets)
  60. // 关闭 ResultSet 对象
  61. closeResultSet(rsw.getResultSet());
  62. }
  63. }

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

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

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

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

简单总结
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执行流程内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号