查看原文
其他

深入MyBatis执行过程

klog SpringForAll 2023-04-28

点击上方☝SpringForAll社区 轻松关注!

及时获取有趣有料的技术文章

MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。

测试环境

POM文件

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.17</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

测试代码

public class ExecutorTest {
 private SqlSessionFactory factory;
    private Configuration configuration;
    private JdbcTransaction jdbcTransaction;
    private Connection connection;
    private MappedStatement ms;
    
    @Before
    public void init() throws SQLException {
        // 获取构建器
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        // 解析XML 并构造会话工厂
        factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml"));
        configuration = factory.getConfiguration();
        // 创建事务
        jdbcTransaction = new JdbcTransaction(factory.openSession().getConnection());
    }
    
        @Test
    public void sessionTest() {
     // 开启会话
        SqlSession sqlSession = factory.openSession(true);
        // 查询数据
        List<Object> list = sqlSession.selectList("com.coderead.mybatis.UserMapper.selectByid", 10);
        // 输出查询结果
        System.out.println(list.get(0));
    }
}

执行过程源码跟踪

经过跟踪分析调用过程后,可以把MyBatis的调用分解为三个过程

开启会话:创建事务、创建Executor

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
   Transaction tx = null;
   try {
     // 获取环境变量
     final Environment environment = configuration.getEnvironment();
     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     // 创建事务
     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     // 创建Executor
     final Executor executor = configuration.newExecutor(tx, execType);
     return new DefaultSqlSession(configuration, executor, autoCommit);
   } catch (Exception e) {
     closeTransaction(tx); // may have fetched a connection so lets call close()
     throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
   } finally {
     ErrorContext.instance().reset();
   }
 }

Executor:管理缓存、创建StatementHandler

CachingExecutor管理二级缓存

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
     throws SQLException {
   Cache cache = ms.getCache();
   if (cache != null) {
     // 当ms.isFlushCacheRequired = true清空二级缓存
     flushCacheIfRequired(ms);
     if (ms.isUseCache() && resultHandler == null) {
       ensureNoOutParams(ms, boundSql);
       @SuppressWarnings("unchecked")
       List<E> list = (List<E>) tcm.getObject(cache, key);
       if (list == null) {
         // 未命中二级缓存,调用BaseExecutor.query()查询数据
         list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // 二级缓存记录新数据
         tcm.putObject(cache, key, list); // issue #578 and #116
       }
       return list;
     }
   }
   // 未命中二级缓存,调用BaseExecutor.query()查询数据
   return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
 }

BaseExecutor管理一级缓存

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.");
   }
   // queryStack记录子嵌套查询深度
   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 {
      // 未命中一级缓存,调用SimpleExecutor.doQuery()查询数据
       list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
   } finally {
     queryStack--;
   }
   if (queryStack == 0) {
     for (DeferredLoad deferredLoad : deferredLoads) {
       deferredLoad.load();
     }
     // issue #601
     deferredLoads.clear();
     // 当一级缓存作用域为STATEMENT时,清空一级缓存。可选值为SESSION
     if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
       // issue #482
       clearLocalCache();
     }
   }
   return list;
 }

SimpleExecutor创建StatementHandler和与之类型匹配的Statement

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());
     // 调用preparedStatement.execute()查询数据
     return handler.query(stmt, resultHandler);
   } finally {
     closeStatement(stmt);
   }
 }

Statement:获取并返回查询结果

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

MyBatis设计模式

Executor

Executor类图

Executor类图

源码展示

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;
  }

StatementHandler

StatementHandler类图

StatementHandler类图

源码展示

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    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());
    }
  }

Statement

Statement类图

Statement类图

源码展示

protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    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() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

后记

此次跟踪MyBatis的一次查询过程,均采用MyBatis默认配置,例如文中提到的ExecutorType默认为SIMPLE,可选值为[SIMPLE, REUSE, BATCH];StatementType默认为PREPARED,可选值为[STATEMENT, PREPARED, CALLABLE],LocalCacheScope默认为SESSION,可选值为[SESSION, STATEMENT]。  Executor和StatementHandler的实现采用装饰器模式。  为了聚焦于查询过程,也选择性忽略了一部分MyBatis的重要功能,如日志模块。

本文来源:http://r6d.cn/bxm5g


 Spring Security 实战干货:微信小程序登录与Spring Security结合的思路分享

 深入理解JVM - Class类文件的结构

 深入剖析线上内存溢出的原因

 后端线上问题排查常用命令收藏

 深入理解JVM - 类加载机制

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存