Mybatis SqlSession 工作原理

1. 使用示例

// 加载全局配置⽂件
InputStream resourceAsStream = Resources.getResourceAsStream("myabtis-config.xml");

// 获得 sqlSession ⼯⼚对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

// 查询
try (SqlSession session = sqlSessionFactory.openSession()) {
  User user = (User) session.selectOne("com.stydu.mybatis.mapper.UserMapper.findById", 101);
}

可以看出 SqlSession 是从 SqlSessionFactory(DefaultSqlSessionFactory) 获取的,SqlSessionFactory 在初始化的时候就已经构建好了,其中已经维护好了一个 Configuration 对象

SqlSession 是个非常重要的工具,是程序与数据库交互的桥梁,提供了一系列方法操作数据库,比如 selectOne、selectList、update、delete、insert 等

还提供了 getMapper 方法来获取 mapper 接口的代理对象,调用 mapper 接口的对象来简化数据库的操作

2. 获取 SqlSession

public class DefaultSqlSessionFactory implements SqlSessionFactory {
	
    // 获取 SqlSession
    public SqlSession openSession() {
        // 第一个参数:执行其类型,默认是 org.apache.ibatis.session.ExecutorType#SIMPLE
        // 第二个参数:事务隔离级别,null 表示使用数据库的隔离级别
        // 第三个参数:true 表示事务自动提交,false 事务需要手动提交
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), 
                                              (TransactionIsolationLevel)null, 
                                              false);
    }
    
    // 生成 SqlSession 的具体做法
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            // 根据环境信息生成一个事务(数据库连接,隔离级别,是否自动提交)
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

            // 生成一个 executor
            Executor executor = this.configuration.newExecutor(tx, execType);

            // 生成一个 sqlSession
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception e) {
        		this.closeTransaction(tx);
        		throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
        		ErrorContext.instance().reset();
        }

        return var8;
    }
}

2.1 生成 Executor 执行器

// org.apache.ibatis.session.Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 保证执行器类型不为空
    executorType = executorType == null ? this.defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    // 创建执行器
    Object 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 (this.cacheEnabled) {
        executor = new CachingExecutor((Executor)executor);
    }

    // 插件套娃
    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
    return executor;
}

// org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
    // mybatis 的插件都会继承 Interceptor 接口,遍历所有插件层层套娃(mvc 的拦截器类似)
    for (Interceptor interceptor : interceptors) {
        // 调用插件的 plugin 方法包装(自己编写插件要实现 plugin 方法)
				target = interceptor.plugin(target);
    }
    return target;
}

2.2 生成 DefaultSqlSession

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
}

Executor 是真正去执行 sql 的,有 4 种类型:简单(SimpleExecutor)、重用(ReuseExecutor)、批量(BatchExecutor),缓存(CacheExecutor)

创建 SqlSession 时会明确执行器是哪种,默认是 SimpleExecutor,如果开启了二级缓存是 CacheExecutor

3. SqlSession 工作原理

SqlSession.selectOne 是 MyBatis 中用于执行查询并返回单个结果的核心方法。它的实现流程涉及多个组件的协作,包括 ExecutorStatementHandlerResultSetHandler


3.1 SqlSession.selectOne 的入口

SqlSession 是 MyBatis 的核心接口,DefaultSqlSession 是其默认实现。selectOne 方法的定义如下:

public interface SqlSession extends Closeable {
    <T> T selectOne(String statement, Object parameter);
}

DefaultSqlSession 中,selectOne 的实现如下:

@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("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}
  • selectOne 方法实际上是调用 selectList 方法,然后检查返回的列表大小。
  • 如果列表大小为 1,返回第一个元素。
  • 如果列表大小大于 1,抛出 TooManyResultsException 异常。
  • 如果列表为空,返回 null

3.2 selectList 的实现

selectListselectOne 的核心实现。在 DefaultSqlSession 中,selectList 的实现如下:

@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);
        // 通过 Executor 执行查询
        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();
    }
}
  • MappedStatement:封装了 SQL 语句的配置信息(如 SQL 语句、参数类型、结果类型等)。
    • id 可以在不同的 Mapper 文件中重复,但必须配合命名空间来确保全限定名唯一。
    • 每个 MappedStatement 的唯一标识是 命名空间 + id,因此可以在不同的 Mapper 中使用相同的 id
  • Executor:执行器,负责执行 SQL 语句。
  • wrapCollection:将参数包装为 MyBatis 内部使用的格式。

3.3 Executor.query 的实现

Executor 是 MyBatis 的执行器接口,BaseExecutor 是其基础实现。query 方法的实现如下:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取 BoundSql
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建 CacheKey
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 执行查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 检查缓存
    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 {
            // 从数据库中查询
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    return list;
}
  • BoundSql:封装了最终的 SQL 语句和参数。
  • CacheKey:用于缓存的键。
  • localCache:本地缓存,用于存储查询结果。

3.4 queryFromDatabase 的实现

queryFromDatabase 是实际执行数据库查询的方法:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    try {
        // 在缓存中占位
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        // 执行查询
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        // 移除占位符
        localCache.removeObject(key);
    }
    // 将结果放入缓存
    localCache.putObject(key, list);
    return list;
}
  • doQuery:实际执行查询的方法。

3.5 doQuery 的实现

doQuerySimpleExecutorBaseExecutor 的子类)中的方法:

@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();
        // 创建 StatementHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 准备 Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 执行查询
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}
  • StatementHandler:负责创建 Statement 并执行 SQL 查询。
  • prepareStatement:准备 Statement,包括设置参数等。

3.6 StatementHandler.query 的实现

StatementHandler 是 MyBatis 的语句处理器接口,PreparedStatementHandler 是其实现类之一。query 方法的实现如下:

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行查询
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
}
  • ps.execute():执行 SQL 查询。
  • resultSetHandler.handleResultSets:处理结果集并映射为 Java 对象。

3.7 ResultSetHandler.handleResultSets 的实现

ResultSetHandler 是 MyBatis 的结果集处理器接口,DefaultResultSetHandler 是其默认实现。handleResultSets 方法的实现如下:

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<>();
    ResultSet rs = null;
    try {
        // 获取结果集
        rs = stmt.getResultSet();
        // 处理结果集
        while (rs != null) {
            multipleResults.add(handleResultSet(rs));
            rs = getNextResultSet(stmt);
        }
    } finally {
        closeResultSet(rs);
    }
    return collapseSingleResultList(multipleResults);
}
  • handleResultSet:将 ResultSet 映射为 Java 对象。
  • collapseSingleResultList:将多个结果集合并为一个列表。

总结

SqlSession.selectOne 的实现流程可以概括为以下步骤:

  1. selectOne 调用 selectList

    • selectOne 方法调用 selectList,并检查返回的列表大小。
  2. selectList 调用 Executor.query

    • 获取 MappedStatement,并通过 Executor 执行查询。
  3. Executor.query 调用 queryFromDatabase

    • 检查缓存,如果缓存中没有结果,则从数据库中查询。
  4. queryFromDatabase 调用 doQuery

    • 实际执行数据库查询。
  5. doQuery 调用 StatementHandler.query

    • 创建 Statement 并执行 SQL 查询。
  6. StatementHandler.query 调用 ResultSetHandler.handleResultSets

    • 处理结果集并映射为 Java 对象。

通过以上流程,SqlSession.selectOne 实现了从数据库查询数据并返回单个结果的功能。

posted @   CyrusHuang  阅读(208)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示