0%

mybatis之SqlSession

MyBatis 核心组件:SqlSession 全解析(创建、实现、线程安全与四大执行对象)

SqlSession 是 MyBatis 中连接开发者与底层数据库操作的核心接口,封装了数据库 CRUD、事务管理、缓存控制等核心能力。本文基于 SqlSession 接口定义、DefaultSqlSession 实现及 SqlSessionManager 源码,从 “接口定位→创建流程→实现逻辑→线程安全→底层协作” 五个维度,彻底拆解 SqlSession 的工作机制,同时关联 Executor 等四大核心对象,帮你掌握 MyBatis 会话管理的本质。

SqlSession 接口:MyBatis 会话的 “入口协议”

SqlSession 相当于 JDBC 中的 Connection,但功能更丰富 —— 不仅是数据库连接的封装,还提供了 Mapper 接口绑定、缓存管理、事务控制等能力。其接口定义按功能可分为 5 大类方法,覆盖所有数据库交互场景。

1. 核心方法分类与解析

(1)查询方法:select* 系列

查询方法是 SqlSession 最常用的功能,支持单结果、多结果、Map 结果、游标(Cursor)等多种返回形式,核心差异在于结果处理方式:

方法签名 功能说明 适用场景
<T> T selectOne(String statement) 执行 SQL,返回单个结果(无参数) 无参单结果查询(如 select count(*)
<T> T selectOne(String statement, Object param) 执行 SQL,传入参数,返回单个结果 有参单结果查询(如 selectById(1)
<E> List<E> selectList(String statement) 执行 SQL,返回结果列表(无参数) 无参多结果查询(如 selectAll()
<E> List<E> selectList(..., RowBounds rb) 传入 RowBounds(起始索引 + 条数),实现逻辑分页 逻辑分页查询(不推荐生产,见前文)
<K,V> Map<K,V> selectMap(..., String mapKey) 将结果列表转为 Map,mapKey 为 Map 的键(需是结果中的某个属性) 需快速通过某个属性查找结果(如按 id 查 User)
<T> Cursor<T> selectCursor(...) 返回游标对象,支持流式读取(避免一次性加载大量数据到内存) 大数据量查询(如导出 10 万条数据)
void select(..., ResultHandler handler) 无返回值,结果由自定义 ResultHandler 处理(灵活控制结果封装) 特殊结果处理(如直接写入文件)

关键细节

  • selectOne() 本质是调用 selectList() 后取第一个元素,若结果集 size > 1 会抛出 TooManyResultsException
  • RowBounds 实现逻辑分页(先查全表再截取),生产环境需用物理分页(如 PageHelper);
  • Cursor 是 MyBatis 3.4+ 新增功能,基于 JDBC 的 ResultSet 流式读取,适合大数据量场景,避免 OOM。
(2)修改方法:insert/update/delete

MyBatis 将 insert、update、delete 统一归为 “修改操作”,底层均委托给 Executor.update() 方法,返回值为受影响的行数

方法签名 功能说明
int insert(String statement, Object param) 执行插入操作,传入参数(如实体对象)
int update(String statement, Object param) 执行更新操作,传入参数(如含主键的实体对象)
int delete(String statement, Object param) 执行删除操作,传入参数(如主键)

关键细节

  • 所有修改操作默认不会自动提交事务(需手动调用 commit()),除非创建 SqlSession 时指定 autoCommit=true
  • 插入操作若需返回自增主键,需在 Mapper 中配置 useGeneratedKeys=true(如 @Options(useGeneratedKeys=true, keyProperty="id"))。
(3)事务方法:commit/rollback

SqlSession 直接封装事务控制逻辑,底层通过 Transaction 对象管理数据库事务:

方法签名 功能说明
void commit() 提交事务(仅当 autoCommit=false 时有效)
void commit(boolean force) 强制提交事务(即使 dirty=false,也会触发提交,用于批量操作)
void rollback() 回滚事务(仅当 autoCommit=false 时有效)
void rollback(boolean force) 强制回滚事务(即使未发生修改,也会回滚,用于异常恢复)

关键概念dirty 标记
DefaultSqlSession 中有 private boolean dirty 属性,用于标记 “当前会话是否有未提交的修改”:

  • 执行 insert/update/delete 后,dirty 会设为 true
  • 调用 commit() 时,若 dirty=falseforce=false,会跳过提交(避免无意义的数据库交互);
  • 调用 rollback() 同理,dirty=falseforce=false 时跳过回滚。
(4)缓存与配置方法

SqlSession 管理 MyBatis 的一级缓存(会话级缓存),并提供配置查询能力:

方法签名 功能说明
void clearCache() 清空当前 SqlSession 的一级缓存(会话级缓存,仅当前会话有效)
Configuration getConfiguration() 获取 MyBatis 全局配置对象(包含数据源、Mapper 映射等所有配置)
Connection getConnection() 获取当前 SqlSession 对应的数据库连接(慎用,避免手动操作连接导致泄漏)
(5)Mapper 接口绑定:getMapper ()

getMapper(Class<T> type) 是 SqlSession 与 Mapper 接口关联的核心方法,通过动态代理生成 Mapper 实现类,避免手动编写实现:

1
2
3
// 示例:获取 UserMapper 代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1);

底层逻辑:委托 Configuration.getMapper()MapperRegistry.getMapper(),最终通过 MapperProxyFactory 生成 MapperProxy 动态代理对象(见前文 Mapper 代理章节)。

SqlSession 的创建者:SqlSessionFactory

SqlSession 不能直接 new 创建,需通过 SqlSessionFactory 工厂接口生成,MyBatis 提供默认实现 DefaultSqlSessionFactory,核心逻辑是 “基于 Configuration 创建 SqlSession”。

1. DefaultSqlSessionFactory:默认工厂实现

DefaultSqlSessionFactory 持有 Configuration 对象,通过两种方式创建 SqlSession:从数据源获取连接(推荐)和用户提供连接(特殊场景)。

(1)方式一:从数据源创建(openSessionFromDataSource)

最常用的方式,通过 MyBatis 配置的数据源(DataSource)获取连接,自动管理事务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 1. 从 Configuration 中获取数据库环境(Environment:数据源 + 事务工厂)
final Environment environment = configuration.getEnvironment();
// 2. 创建事务工厂(TransactionFactory),默认 JdbcTransactionFactory
final TransactionFactory txFactory = getTransactionFactoryFromEnvironment(environment);
// 3. 创建 Transaction 对象(封装数据库连接和事务隔离级别)
tx = txFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 4. 创建 Executor 执行器(核心执行组件,按 execType 选择类型)
final Executor executor = configuration.newExecutor(tx, execType);
// 5. 创建 DefaultSqlSession(持有 Configuration、Executor、autoCommit)
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // 异常时关闭事务,避免连接泄漏
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

关键参数解析

  • ExecutorType:执行器类型,决定 SQL 执行策略(见下表);
  • TransactionIsolationLevel:事务隔离级别(如 READ_COMMITTEDREPEATABLE_READ),默认使用数据库全局隔离级别;
  • autoCommit:是否自动提交事务,默认 false(MyBatis 手动管理事务)。
ExecutorType 核心特点 适用场景
SIMPLE 默认类型,每次执行 SQL 都创建新的 PreparedStatement 大多数单条 SQL 执行场景
REUSE 复用 PreparedStatement(按 SQL 缓存) 重复执行相同 SQL(如循环查询同一 SQL)
BATCH 批量执行 INSERT/UPDATE/DELETE,仅提交一次事务 批量操作(如批量插入 1000 条数据)
(2)方式二:用户提供连接(openSessionFromConnection)

特殊场景下(如集成第三方事务管理器),用户手动提供数据库连接,SqlSession 基于该连接创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
// 1. 获取连接的自动提交状态(默认 true,若连接已设置则沿用)
boolean autoCommit = connection.getAutoCommit();
// 2. 创建 Transaction(基于用户提供的连接,不自动管理连接生命周期)
final Environment environment = configuration.getEnvironment();
final TransactionFactory txFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = txFactory.newTransaction(connection);
// 3. 创建 Executor 和 DefaultSqlSession
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();
}
}

注意:用户需手动管理连接的关闭(SqlSession 关闭时不会关闭该连接),避免连接泄漏。

2. SqlSessionFactory 的常用方法

SqlSessionFactory 提供多个 openSession() 重载方法,满足不同场景需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 默认:ExecutorType.SIMPLE,默认隔离级别,autoCommit=false
SqlSession openSession();

// 2. 指定是否自动提交
SqlSession openSession(boolean autoCommit);

// 3. 指定 ExecutorType(如批量执行)
SqlSession openSession(ExecutorType execType);

// 4. 指定事务隔离级别
SqlSession openSession(TransactionIsolationLevel level);

// 5. 指定 ExecutorType 和自动提交
SqlSession openSession(ExecutorType execType, boolean autoCommit);

// 6. 指定 ExecutorType 和隔离级别
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

// 7. 基于用户提供的连接创建
SqlSession openSession(Connection connection);

SqlSession 的默认实现:DefaultSqlSession

DefaultSqlSession 是 SqlSession 接口的核心实现,所有方法最终都委托给 Executor 执行,其核心属性和方法逻辑如下:

1. 核心属性

1
2
3
4
5
6
7
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration; // 全局配置对象(唯一)
private final Executor executor; // 执行器(SQL 执行核心)
private final boolean autoCommit; // 是否自动提交事务
private boolean dirty; // 是否有未提交的修改(脏数据标记)
private List<Cursor<?>> cursorList; // 管理游标对象,关闭时统一释放
}
  • cursorList:收集当前 SqlSession 创建的所有 Cursor 对象,在 close() 时统一关闭,避免游标泄漏;
  • dirty:执行 insert/update/delete 后设为 truecommit()/rollback() 后设为 false,用于优化事务操作(无修改时跳过提交)。

2. 核心方法逻辑拆解

(1)查询方法:selectOne ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public <T> T selectOne(String statement, Object parameter) {
// 1. 调用 selectList() 获取结果列表
List<T> list = this.selectList(statement, parameter);
// 2. 结果列表 size 为 1 则返回第一个元素,否则抛出异常
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; // 无结果返回 null
}
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 1. 从 Configuration 中获取 MappedStatement(封装 SQL 元信息)
MappedStatement ms = configuration.getMappedStatement(statement);
// 2. 委托 Executor.query() 执行查询,传入 ResultHandler(默认 null)
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();
}
}

关键wrapCollection(parameter) 用于处理集合类型参数(如 List、Array),将其封装为 Map(键为 collection/array),便于后续 ParameterHandler 处理。

(2)修改方法:insert ()/update ()/delete ()

所有修改方法最终都委托给 update() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}

@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}

@Override
public int update(String statement, Object parameter) {
try {
dirty = true; // 标记为有未提交修改
MappedStatement ms = configuration.getMappedStatement(statement);
// 委托 Executor.update() 执行修改操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
(3)事务方法:commit ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void commit() {
commit(false); // 默认不强制提交
}

@Override
public void commit(boolean force) {
try {
// 1. 委托 Executor 提交事务
executor.commit(isCommitOrRollbackRequired(force));
dirty = false; // 提交后重置脏数据标记
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

// 判断是否需要提交:force=true 或 dirty=true(有未提交修改)
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
(4)关闭方法:close ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public void close() {
try {
// 1. 关闭 Executor(释放连接、关闭游标、提交/回滚事务)
executor.close(isCommitOrRollbackRequired(false));
// 2. 统一关闭所有游标
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}

private void closeCursors() {
if (cursorList != null && !cursorList.isEmpty()) {
for (Cursor<?> cursor : cursorList) {
try {
cursor.close();
} catch (IOException e) {
throw ExceptionFactory.wrapException("Error closing cursor. Cause: " + e, e);
}
}
cursorList.clear();
}
}

关键executor.close() 会根据 autoCommitdirty 决定是否提交事务(若 autoCommit=falsedirty=true,会自动回滚未提交的事务,避免数据不一致)。

SqlSession 的线程安全增强:SqlSessionManager

SqlSessionManager 是 MyBatis 提供的线程安全的 SqlSession 管理类,同时实现 SqlSessionFactorySqlSession 接口,解决了 DefaultSqlSession 线程不安全的问题(DefaultSqlSession 不能多线程共享)。

1. 核心设计:ThreadLocal 绑定 + 动态代理

SqlSessionManager 通过两个核心组件实现线程安全:

  • ThreadLocal<SqlSession> localSqlSession:将 SqlSession 与当前线程绑定,确保同一线程复用同一个 SqlSession;
  • SqlSession sqlSessionProxy:通过 JDK 动态代理生成 SqlSession 代理对象,自动判断当前线程是否已绑定 SqlSession,决定复用或创建新对象。
核心属性
1
2
3
4
5
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory; // 底层依赖的工厂
private final SqlSession sqlSessionProxy; // SqlSession 动态代理
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>(); // 线程绑定的 SqlSession
}

2. 两种工作模式

SqlSessionManager 通过 SqlSessionInterceptor(实现 InvocationHandler)实现两种工作模式,逻辑在 invoke() 方法中:

(1)模式一:线程绑定模式(复用 SqlSession)

若当前线程已通过 startManagedSession() 绑定 SqlSession,则直接复用该对象,避免重复创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 绑定 SqlSession 到当前线程
public void startManagedSession() {
this.localSqlSession.set(openSession());
}

// 代理逻辑:优先使用线程绑定的 SqlSession
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 从 ThreadLocal 中获取当前线程绑定的 SqlSession
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
// 2. 复用 SqlSession,调用对应方法
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} else {
// 模式二:创建新 SqlSession(见下文)
try (SqlSession autoSqlSession = openSession()) {
try {
final Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
return result;
} catch (Throwable t) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
}
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 创建 SqlSessionManager
SqlSessionManager manager = SqlSessionManager.newInstance(sqlSessionFactory);

// 2. 绑定 SqlSession 到当前线程(当前线程后续复用该对象)
manager.startManagedSession();

try {
// 3. 多次获取 SqlSession,实际是同一个对象
UserMapper mapper1 = manager.getMapper(UserMapper.class);
UserMapper mapper2 = manager.getMapper(UserMapper.class);
mapper1.selectById(1);
mapper2.updateUser(user);

// 4. 手动提交事务
manager.commit();
} finally {
// 5. 关闭 SqlSession 并从线程解绑
manager.close();
manager.localSqlSession.remove();
}
(2)模式二:自动创建模式(每次新对象)

若当前线程未绑定 SqlSession,则自动创建新的 SqlSession,使用 try-with-resources 确保自动关闭,避免泄漏:

1
2
3
4
5
6
// 示例:直接调用 SqlSessionManager 的方法,无需手动绑定
try (SqlSessionManager manager = SqlSessionManager.newInstance(sqlSessionFactory)) {
UserMapper mapper = manager.getMapper(UserMapper.class);
User user = mapper.selectById(1);
// 自动提交事务(代理逻辑中会调用 autoSqlSession.commit())
}

3. 与 DefaultSqlSessionFactory 的区别

特性 DefaultSqlSessionFactory SqlSessionManager
线程安全 不安全(每次 openSession () 都创建新对象) 安全(ThreadLocal 绑定线程,复用 SqlSession)
事务管理 需手动管理 SqlSession 的 commit/close 支持自动提交(模式二)和手动管理(模式一)
性能 多线程频繁创建 / 关闭 SqlSession,性能损耗 同一线程复用 SqlSession,减少开销
适用场景 大多数 Spring 环境(Spring 管理 SqlSession) 非 Spring 环境、多线程场景(如自定义线程池)

SqlSession 的底层支撑:四大核心执行对象

SqlSession 本身不直接执行 SQL,而是通过 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四大对象协作完成,这四个对象被称为 MyBatis 的 “执行引擎”。

1. 四大对象的职责与协作流程

(1)Executor:执行器(“总指挥”)
  • 职责:调度其他三个对象,负责 SQL 执行的整体流程(缓存查询、事务管理、SQL 调用);
  • 核心方法query()(查询)、update()(修改)、commit()(提交)、rollback()(回滚);
  • 与 SqlSession 的关系:SqlSession 持有 Executor 对象,所有方法最终委托给 Executor 执行。
(2)StatementHandler:SQL 执行处理器(“执行者”)
  • 职责:与数据库直接交互,创建 JDBC 的 Statement/PreparedStatement,执行 SQL 并返回 ResultSet
  • 核心方法prepare()(预编译 SQL)、parameterize()(设置参数)、query()/update()(执行 SQL);
  • 创建逻辑:由 Executor 的 newStatementHandler() 方法创建,默认 RoutingStatementHandler(根据 SQL 类型选择具体实现)。
(3)ParameterHandler:参数处理器(“参数转换器”)
  • 职责:将 Mapper 方法的参数(如 user.getId())转换为 JDBC 所需的参数(PreparedStatement.setXxx());
  • 核心方法setParameters()(设置 SQL 占位符参数);
  • 创建逻辑:由 StatementHandler 初始化时创建,默认 DefaultParameterHandler
(4)ResultSetHandler:结果处理器(“结果转换器”)
  • 职责:将 JDBC 的 ResultSet 解析为 Java 实体对象(或 List/Map),处理结果映射;
  • 核心方法handleResultSets()(解析结果集)、handleOutputParameters()(处理存储过程输出参数);
  • 创建逻辑:由 StatementHandler 初始化时创建,默认 DefaultResultSetHandler

2. 四大对象协作流程(以 selectOne 为例)

SqlSession 最佳实践

1. 生命周期管理:单次请求,及时关闭

  • 生命周期:SqlSession 应与 “单次业务请求” 绑定,使用后立即关闭,避免长期占用连接;

  • 关闭方式:优先使用try-with-resources(自动关闭),避免忘记关闭导致连接泄漏:

    1
    2
    3
    4
    5
    6
    7
    8
    // 推荐:try-with-resources 自动关闭 SqlSession
    try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    // 业务逻辑
    session.commit(); // 手动提交事务
    } catch (Exception e) {
    // 异常处理
    }

2. 线程安全:禁止多线程共享 SqlSession

  • DefaultSqlSession 线程不安全(无同步机制,dirty 标记会被多线程篡改),每个线程应单独创建 SqlSession;
  • 若需多线程复用,使用 SqlSessionManager 的线程绑定模式,或依赖 Spring(Spring 会为每个线程注入独立的 SqlSession)。

3. 一级缓存:理解缓存范围与失效场景

  • SqlSession 的一级缓存是 “会话级”,仅当前 SqlSession 有效,关闭后缓存清空;
  • 缓存失效场景:执行 insert/update/delete、调用 clearCache()、关闭 SqlSession;
  • 注意:一级缓存可能导致 “脏读”(如同一 SqlSession 中,其他线程修改了数据,当前会话仍读取缓存),若需禁用,可在 Mapper 中配置 flushCache="true"

4. 事务管理:手动提交 vs 自动提交

  • 生产环境建议 autoCommit=false(手动管理事务),避免部分修改成功后自动提交,导致数据不一致;

  • 批量操作(如批量插入)使用ExecutorType.BATCH,并在最后统一提交,减少事务开销:

    1
    2
    3
    4
    5
    6
    7
    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    for (User user : userList) {
    mapper.insertUser(user);
    }
    session.commit(); // 批量提交,仅一次事务
    }

总结

SqlSession 是 MyBatis 会话管理的核心,其本质是 “数据库连接 + 执行器 + 配置” 的封装:

  1. 入口:通过 SqlSessionFactory 创建,支持从数据源或用户提供的连接生成;
  2. 实现DefaultSqlSession 是默认实现,所有方法委托给 Executor 执行,需注意线程安全;
  3. 增强SqlSessionManager 通过 ThreadLocal 实现线程安全,支持同一线程复用 SqlSession;
  4. 底层:依赖四大执行对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)完成 SQL 执行与结果处理

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10