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 会引出其他三大对象来完成数据库的读写。


相关文章
|
1月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
30 10
|
3月前
|
XML Java 数据库连接
MyBatis中的接口代理机制及其使用
【8月更文挑战第5天】MyBatis的接口代理机制是其核心功能之一,允许通过定义接口并在运行时生成代理对象来操作数据库。开发者声明一个带有`@Mapper`注解的接口,MyBatis则依据接口方法、映射配置(XML或注解)及数据库信息动态生成代理类。此机制分为四步:创建接口、配置映射文件或使用注解、最后在业务逻辑中注入并使用代理对象。这种方式简化了数据库操作,提高了代码的可读性和可维护性。例如,在电商系统中可通过`OrderMapper`处理订单数据,在社交应用中利用`MessageMapper`管理消息,实现高效且清晰的数据库交互。
|
4月前
|
Java 数据库连接 Maven
文本,使用SpringBoot工程创建一个Mybatis-plus项目,Mybatis-plus在编写数据层接口,用extends BaseMapper<User>继承实体类
文本,使用SpringBoot工程创建一个Mybatis-plus项目,Mybatis-plus在编写数据层接口,用extends BaseMapper<User>继承实体类
|
4月前
|
SQL Java 数据库连接
Mybatis之SqlSession简析
Mybatis之SqlSession简析
148 0
MybatisPlus--IService接口基本用法,MP提供了Service接口,save(T) 这里的意思是新增了一个T, saveBatch 是批量新增的意思,saveOrUpdate是增或改
MybatisPlus--IService接口基本用法,MP提供了Service接口,save(T) 这里的意思是新增了一个T, saveBatch 是批量新增的意思,saveOrUpdate是增或改
|
4月前
|
XML Java 数据格式
支付系统----微信支付20---创建案例项目--集成Mybatis-plus的补充,target下只有接口的编译文件,xml文件了,添加日志的写法
支付系统----微信支付20---创建案例项目--集成Mybatis-plus的补充,target下只有接口的编译文件,xml文件了,添加日志的写法
接口模板,文本常用的接口Controller层,常用的controller层模板,Mybatisplus的相关配置
接口模板,文本常用的接口Controller层,常用的controller层模板,Mybatisplus的相关配置
|
5月前
|
Java 数据库连接 mybatis
使用Mybatis获取sqlSession对象老爆红的问题解决
使用Mybatis获取sqlSession对象老爆红的问题解决
|
5月前
|
存储 Java 数据库连接
SSMP整合案例第三步 业务层service开发及基于Mybatis的接口功能拓展
SSMP整合案例第三步 业务层service开发及基于Mybatis的接口功能拓展
37 0
|
6月前
|
SQL
【MybatisPlus】条件构造器、自定义SQL、Service接口
【MybatisPlus】条件构造器、自定义SQL、Service接口
98 0
【MybatisPlus】条件构造器、自定义SQL、Service接口