MyBatis之魂:探索核心接口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 会引出其他三大对象来完成数据库的读写。


相关文章
|
4月前
|
SQL Java 数据库连接
Mybatis之Mybatis简介、搭建Mybatis相关步骤(开发环境、maven、核心配置文件、mapper接口、映射文件、junit测试、log4j日志)
【1月更文挑战第2天】 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
204 3
Mybatis之Mybatis简介、搭建Mybatis相关步骤(开发环境、maven、核心配置文件、mapper接口、映射文件、junit测试、log4j日志)
|
4月前
|
测试技术 数据库
深入探索MyBatis-Plus中Service接口的lambdaUpdate用法及示例
深入探索MyBatis-Plus中Service接口的lambdaUpdate用法及示例
260 0
|
6天前
|
Java 数据库连接 数据库
MybatisPlus中IService接口有什么用?
MybatisPlus中IService接口有什么用?
|
6天前
|
Java 数据库连接 mybatis
MyBatis中Mapper接口和dao区别是什么?
MyBatis中Mapper接口和dao区别是什么?
|
2月前
|
XML SQL Java
Mybatis接口Mapper内的方法为啥不能重载吗
Mybatis接口Mapper内的方法为啥不能重载吗
20 0
|
2月前
Mybatis+mysql动态分页查询数据案例——房屋信息的接口(IHouseDao)
Mybatis+mysql动态分页查询数据案例——房屋信息的接口(IHouseDao)
12 1
|
2月前
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
9 0
|
2月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
7 0
|
3月前
|
SQL Java 数据库连接
一篇看懂Mybatis的SqlSession运行原理
SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式),那么它是如何执行实现的,这就是本篇博文所介绍的东西,其中会涉及到简单的源码讲解。
35 1