MyBatis之魂:探索核心接口SqlSession的神秘力量

举报
薛伟同学 发表于 2024/12/20 11:45:11 2024/12/20
【摘要】 作为MyBatis框架中最重要的核心接口之一,SqlSession承担着数据操作的关键任务。本文将深入研究SqlSession接口,解析其在MyBatis中的角色和功能。我们将探讨SqlSession的生命周期、作用域、数据操作方法等关键特性,剖析其在数据库会话管理、事务控制等方面的实现原理。

SqlSession

SqlSession 是 MyBatis 对外暴露的最核心接口,用于操作 SQL 的执行,以屏蔽掉底层 JDBC 操作数据库的繁琐,可以直接调用其申明的各种方便的方法如,查询/新增/更新/删除/提交事务/回滚事务/获取 Mapper 代理对象等。

接口定义

public interface SqlSession extends Closeable {

    // 查询单个结果
    <T> T selectOne(String statement);
    <T> T selectOne(String statement, Object parameter);
    
    // 查询 List<E> 结果,可指定 RowBounds 参数
    <E> List<E> selectList(String statement);
    <E> List<E> selectList(String statement, Object parameter);
    <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
    
    // 返回 Map 的查询方式
    <K, V> Map<K, V> selectMap(String statement, String mapKey);
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
    
    // 返回懒处理迭代器 Cursor 游标的查询,注意这种方式的查询非常适合与数百万数据项的查询(不会直接加载进内存中)
    <T> Cursor<T> selectCursor(String statement);
    <T> Cursor<T> selectCursor(String statement, Object parameter);
    <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
    
    // 指定 resultHandler 参数的查询
    void select(String statement, Object parameter, ResultHandler handler);
    void select(String statement, ResultHandler handler);
    void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
    
    // insert 相关
    int insert(String statement);
    int insert(String statement, Object parameter);
    
    // update 相关
    int update(String statement);
    int update(String statement, Object parameter);
    
    // delete 相关
    int delete(String statement);
    int delete(String statement, Object parameter);
    
    // 事务相关,force 参数代表是否强制提交/回滚事务
    void commit();
    void commit(boolean force);
    void rollback();
    void rollback(boolean force);
    
    // 获取当前 MyBatis 配置对象
    Configuration getConfiguration();

    // 获取一个绑定到当前 SqlSession 对象的 type 类型的 Mapper 代理对象
    <T> T getMapper(Class<T> type);

    // 获取内部持有的数据库连接对象
    Connection getConnection();
}

继承关系

实现类

SqlSession 接口的主要实现类是 DefaultSqlSession,SqlSessionFactory 的主要实现是 DefaultSqlSessionFactory。不过从类图我们看到有一个SqlSessionManager 类,他实现了两个接口,一方面具备生产 SqlSession 的能力,另一方面又具备 SqlSession 的能力。

我们比较一下两个实现类的区别:

  1. DefaultSqlSession:每次访问数据库创建一个新的 SqlSession,一个 SqlSession 代表一次数据库连接(默认,也是我们重点分析的)。
  2. SqlsessionManager:内部通过 ThreadLocal 来保证一个线程只创建一个 SqlSession,但是也可以每次创建一个新的(支持两种模式)。

DefaultSqlSession

SqlSession 的默认实现是 DefaultSqlSession。SqlSession 提供的数据库操作在底层都是调用 Executor 来执行。

类定义
public class DefaultSqlSession implements SqlSession {...}

可以看到这里 DefaultSqlSession 仅实现了 SqlSession 接口。

内部属性定义
// 重要属性,持有 MyBatis Configuration 实例
private final Configuration configuration;

// 持有执行器 Executor,后面会详细分析
private final Executor executor;

// 是否自动提交事务
private final boolean autoCommit;

// 是否已过期,执行 update 操作就会置为 true
private boolean dirty;

// 当查询数以百万计的数据时用到
private List<Cursor<?>> cursorList;

每个 SqlSession 实例内部都持有一个 MyBatis 的配置实例以及一个执行器 Executor 实例,且在其构造函数中就需要初始化这两个属性了。

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

public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
}
核心代码
// ...

@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    // selectMap 底层走的还是 selectList 方法
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    // 因为是按照 resultMap 的形式返回,因此拿到结果集之后,需要对结果进行处理,通过 mapResultHandler 处理
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
                                                                                             configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<V>();
    for (V o : list) {
        context.nextResultObject(o);
        mapResultHandler.handleResult(context);
    }
    // 返回处理后的结果集
    return mapResultHandler.getMappedResults();
}

@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
        registerCursor(cursor);
        return cursor;
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

@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("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

@Override
public int update(String statement, Object parameter) {
    try {
        dirty = true;
        MappedStatement ms = configuration.getMappedStatement(statement);
        return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

// ...

SqlSession 提供很多增删改查的方法,在 DefaultSqlSession 中看方法执行流程,所有的查询方法最后都会走到一个方法,底层调用 Executor 的 query,更新和删除也类似,都会走到 Executor 的更新方法。

这些核心方法的基本逻辑如下:

  1. 首先从 configuratin 实例中获取到 MappedStatement 实例。
  2. 调用执行器 executor 的相关方法,并传入刚刚得到的 MappedStatement 实例作为参数。

SqlSession 真正的 SQL 执行交给了 Executor 执行器,前文不是说过 SqlSession 是 MyBatis 提供的顶层 API 嘛,通过 SqlSession 就可以操作数据库的各种 SQL 操作了,这里怎么又出来一个执行器呢?实际上,MyBatis的 SqlSession 非常聪明,将各种 SQL 操作的脏活、累活都委托给了 Executor 执行器干,自己则坐享其成。

SqlSessionFactory

SqlSessionFactory 是创建 SqlSession 的工厂。

接口定义

public interface SqlSessionFactory {

    SqlSession openSession();

    SqlSession openSession(boolean autoCommit);
    SqlSession openSession(Connection connection);
    SqlSession openSession(TransactionIsolationLevel level);

    SqlSession openSession(ExecutorType execType);
    SqlSession openSession(ExecutorType execType, boolean autoCommit);
    SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
    SqlSession openSession(ExecutorType execType, Connection connection);

    Configuration getConfiguration();
}

继承关系

实现类

DefaultSqlSessionFactory

DefaultSqlSessionFactory 是 SqlSessionFactory 的重要实现类,它是创建 SqlSession 的工厂。创建 SqlSession 有两种方式,从数据源获取或者从数据库连接获取。DefaultSqlSessionFactory 提供了很多创建的方法,底层最后都是走这两个方法,具体看下面代码和注释:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 从数据源 DataSource 获取 Transaction,这是两者最大的区别,其他的都差不多
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        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();
    }
}

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
        boolean autoCommit;
        try {
            autoCommit = connection.getAutoCommit();
        } catch (SQLException e) {
            // Failover to true, as most poor drivers
            // or databases won't support transactions
            autoCommit = true;
        }      
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 从连接对象获取 Transaction,这是两者最大的区别,其他的都差不多
        final Transaction tx = transactionFactory.newTransaction(connection);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

总结一下

  1. SqlSession 是用户使用 MyBatis 编程的关键接口,我们通过 SqlSessionFactory 获取 SqlSession。
  2. SqlSessionFactory 的初始化过程在配置初始化阶段完成,之后就可以通过 SqlSessionFactory 获取 SqlSession 了。
  3. 通过 SqlSession,我们可以获取接口的代理对象,继而进行增删改查操作,经过一层代理之后,真正的查询入口还是 SqlSession 的方法。
  4. SqlSession 中很多数据库读取的重载方法,这些方法最后都走到一个方法里面,并且会使用 Executor 组件去访问数据库,Executor 会引出其他三大对象来完成数据库的读写。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。