面试官:SqlSession 是什么?它和 SqlSessionFactory 是什么关系?SqlSession 里的四大对象是什么时候创建的?
一、回顾与开篇
前几篇我们走通了整体架构,也看了配置加载和 Mapper 代理。今天我们把目光聚焦到SqlSession这个“门面”上。
先看一段最熟悉的代码:
// 1. 构建 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);// 2. 打开 SqlSessionSqlSession session = factory.openSession();// 3. 获取 Mapper 并执行UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.selectById(1);// 4. 关闭会话session.close();
第 2 步和第 4 步就是今天的主角:SqlSession 的创建与销毁。
本篇目标:
- 搞清楚
SqlSessionFactory的构建过程 - 深入
DefaultSqlSession的核心实现 - 理清四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)的初始化时机
- 理解一级缓存与 SqlSession 的绑定关系
一句话总结:
SqlSessionFactory是生产SqlSession的工厂SqlSession是MyBatis的门面接口,对外提供统一的数据库操作API。具体执行时,SqlSession将操作委托给Executor执行器,由Executor统一调度StatementHandler、ParameterHandler和ResultSetHandler三大组件完成SQL执行与结果映射。DefaultSqlSession是 MyBatis 原生提供的默认实现类,也是最常用的实现。除此之外,MyBatis 还提供了SqlSessionManager实现。在 Spring 整合环境中,核心实现则是SqlSessionTemplate
本文主要分析MyBatis原生框架中SqlSession的核心设计与实现。第3章补充说明在Spring整合环境下的额外机制(Mapper扫描、SqlSessionTemplate等),阅读时请注意区分纯MyBatis原生逻辑与Spring整合层逻辑
二、SqlSessionFactory 构建流程
2.1 从 builder.build() 说起
// SqlSessionFactoryBuilderpublic SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);Configuration config = parser.parse(); // 解析配置return build(config); // 构建 SqlSessionFactory} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
非常简单:解析完 Configuration 后,直接 new DefaultSqlSessionFactory(config)。
2.2 DefaultSqlSessionFactory 的结构
// DefaultSqlSessionFactorypublic class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}// 各种 openSession 重载方法,最终都调用 openSessionFromDataSource@Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}@Overridepublic SqlSession openSession(boolean autoCommit) {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);}@Overridepublic SqlSession openSession(ExecutorType execType) {return openSessionFromDataSource(execType, null, false);}// ... 其他重载}
关键点:DefaultSqlSessionFactory 只是 Configuration 的持有者,不保存任何会话状态,所以是线程安全的,可以被多个线程共享。
三、@MapperScan 底层注册流程
3.1 整体设计理念:延迟扫描
在 3.0.x 版本中,@MapperScan的设计理念是延迟扫描 + 职责分离:
角色 | 实现类 | 核心职责 |
触发者 |
| 声明扫描范围,通过 导入 Registrar |
定义注册者 |
| 注册 的 BeanDefinition |
扫描执行者 |
| 实现 ,在 Spring 生命周期中触发扫描 |
核心扫描器 |
| 执行真正的包扫描和 BeanDefinition 转换 |
3.2 阶段一:MapperScannerRegistrar — 注册扫描器定义
@MapperScan 注解通过 @Import 导入了 MapperScannerRegistrar
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Import(MapperScannerRegistrar.class)public @interface MapperScan {String[] basePackages() default {};// ... 其他属性}
MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,Spring 启动时会调用其 registerBeanDefinitions 方法
@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {// 1. 获取 @MapperScan 注解的所有属性AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {// 2. 调用重载方法,传入生成的基名this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,generateBaseBeanName(importingClassMetadata, 0));}}private void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,AnnotationAttributes mapperScanAttrs,BeanDefinitionRegistry registry, String beanName) {// 3. 创建 MapperScannerConfigurer 的 BeanDefinitionBuilderBeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);// 4. 设置 basePackage(扫描路径)builder.addPropertyValue("basePackage",StringUtils.collectionToCommaDelimitedString(basePackages));// 5. 其他属性设置(sqlSessionFactoryBeanName、annotationClass 等)// 6. 注册到 Spring 容器,此时扫描尚未执行registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}
核心要点:
MapperScannerRegistrar只注册MapperScannerConfigurer的 BeanDefinition,不执行扫描- 扫描动作被推迟到 Spring 后续的生命周期中执行
3.3 阶段二:MapperScannerConfigurer — 触发扫描
MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,这是 Spring 提供的核心扩展点,允许在 Bean 实例化之前修改或新增 BeanDefinition。
Spring 在 refresh() 容器的 invokeBeanDefinitionRegistryPostProcessors 阶段,会回调 postProcessBeanDefinitionRegistry 方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {// 1. 处理占位符(如 ${basePackage})if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders();}// 2. 创建 ClassPathMapperScannerClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);// 3. 设置各项属性scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);// 4. 注册过滤器(决定哪些接口被扫描)scanner.registerFilters();// 5. 执行扫描(核心动作)scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}
执行时机:此方法在容器解析完所有 XML 和注解配置之后、任何 Bean 实例化之前执行,是 Spring 提供的最后添加 BeanDefinition 的地方
3.4 阶段三:ClassPathMapperScanner — 扫描与 BeanDefinition 转换
ClassPathMapperScanner 继承自 Spring 的 ClassPathBeanDefinitionScanner,重写了 doScan 方法:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;@Overridepublic Set<BeanDefinitionHolder> doScan(String... basePackages) {// 1. 调用父类进行标准组件扫描,得到原始 BeanDefinition(beanClass = 接口本身)Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (!beanDefinitions.isEmpty()) {// 2. 对扫描到的 BeanDefinition 进行二次处理(关键步骤)processBeanDefinitions(beanDefinitions);}return beanDefinitions;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {for (BeanDefinitionHolder holder : beanDefinitions) {BeanDefinition definition = holder.getBeanDefinition();// 3. 修改 beanClass:将接口类型改为 MapperFactoryBeandefinition.setBeanClass(this.mapperFactoryBeanClass);// 4. 添加构造参数:原始 Mapper 接口类型definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());// 5. 添加属性:sqlSessionFactory 或 sqlSessionTemplateif (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);}if (this.sqlSessionTemplate != null) {definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);}}}}
关键转换:
转换前 | 转换后 |
beanName = | beanName = (不变) |
beanClass = (接口) | beanClass = |
无构造参数 | 构造参数 = |
3.5 阶段四:MapperFactoryBean — 生成代理对象
经过上述转换,Spring 容器中每个 Mapper 接口都对应一个MapperFactoryBean的 BeanDefinition。
MapperFactoryBean 实现了 FactoryBean 接口,Spring 实例化时会调用 getObject() 方法获取真正的 Mapper 代理对象:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic T getObject() throws Exception {// 通过 SqlSession 获取 Mapper 代理对象return getSqlSession().getMapper(this.mapperInterface);}}
getSqlSession().getMapper()的调用链最终进入 MyBatis 核心:
SqlSessionTemplate.getMapper()→ Configuration.getMapper()→ MapperRegistry.getMapper()→ MapperProxyFactory.newInstance()→ Proxy.newProxyInstance() → 生成 MapperProxy
最终,Spring 容器中存入的是 MapperProxy 动态代理对象,这就是为什么我们可以直接用 @Autowired 注入一个接口的原因。
3.6 完整调用链总览
启动类 @MapperScan("com.example.mapper")↓@Import(MapperScannerRegistrar.class)↓MapperScannerRegistrar.registerBeanDefinitions()→ 注册 MapperScannerConfigurer 的 BeanDefinition↓Spring refresh() → invokeBeanDefinitionRegistryPostProcessors↓MapperScannerConfigurer.postProcessBeanDefinitionRegistry()→ 创建 ClassPathMapperScanner→ 调用 scanner.scan(basePackages)↓ClassPathMapperScanner.doScan()→ super.doScan() → 父类扫描包,发现 Mapper 接口→ processBeanDefinitions()→ 修改 BeanDefinition:beanClass = MapperFactoryBean→ 添加构造参数:原始 Mapper 接口类型↓MapperFactoryBean.getObject()→ sqlSession.getMapper(mapperInterface)→ MapperRegistry.getMapper()→ MapperProxyFactory.newInstance()→ JDK 动态代理 → MapperProxy↓Spring 容器注入 MapperProxy 代理对象
四、DefaultSqlSession 核心源码拆解
4.1 核心字段
public classDefaultSqlSessionimplementsSqlSession{private final Configuration configuration; // 全局配置private final Executor executor; // 执行器private final boolean autoCommit; // 是否自动提交private boolean dirty; // 是否有数据变更(用于判断是否需要提交/回滚)private List<Cursor<?>> cursors; // 游标集合,用于 close 时批量关闭// ... 其他}
4.2 查询方法链路
以 selectOne 为例:
// DefaultSqlSession@Overridepublic <T> T selectOne(String statement, Object parameter) {// 委托给 selectList,然后取第一个元素List<T> list = this.selectList(statement, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("...");} else {return null;}}@Overridepublic <E> List<E> selectList(String statement, Object parameter) {return this.selectList(statement, parameter, RowBounds.DEFAULT);}@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {// 1. 从 Configuration 中获取 MappedStatementMappedStatement ms = configuration.getMappedStatement(statement);// 2. 委托给 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();}}
关键点:
selectOne本质是selectList的包装,取第一个元素statement参数是 Mapper XML 中的namespace.methodId- 参数如果是集合类型,会被包装(避免参数名解析问题)
4.3 更新方法链路
以 insert / update / delete 为例,它们都走 update 方法:
// DefaultSqlSession@Overridepublic int insert(String statement, Object parameter) {return update(statement, parameter);}@Overridepublic 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();}}
关键点:dirty标志用于标记当前SqlSession是否执行过任何数据变更操作(insert/update/delete),是事务提交判断的核心依据:
autoCommit = false时,commit()方法会检查isCommitOrRollbackRequired方法,若dirty为true则提交事务并将dirty重置为false;close()方法同理,若dirty为true则回滚事务。autoCommit = true时,dirty标志不影响提交/回滚,因为数据库事务由JDBC自动提交管理。
需要注意的是,以上是纯MyBatis原生机制。在Spring整合场景下,事务由Spring的PlatformTransactionManager统一管理,不应再手动调用sqlSession.commit()。
4.4 事务管理:commit / rollback / close
// DefaultSqlSession@Overridepublic void commit() {commit(false);}@Overridepublic void commit(boolean force) {try {// 提交事务,force 参数表示即使不 dirty 也提交executor.commit(isCommitOrRollbackRequired(force));dirty = false; // 提交成功后重置 dirty} catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}@Overridepublic void rollback() {rollback(false);}@Overridepublic void rollback(boolean force) {try {// 回滚事务executor.rollback(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}@Overridepublic void close() {try {// 如果有未提交的变更,回滚if (dirty) {executor.rollback(true);}// 关闭 executor(会关闭连接)executor.close(true);dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error closing session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}// 判断是否需要提交/回滚private boolean isCommitOrRollbackRequired(boolean force) {return (!autoCommit && dirty) || force;}
事务逻辑总结:
场景 | autoCommit=false(默认) | autoCommit=true |
执行更新后 | dirty=true,需要手动 commit | 自动提交,dirty 变化不影响 |
close() 时 | 如果 dirty=true,自动 rollback | 直接关闭 |
commit(force) | 正常提交 | 即使 autoCommit=true 也提交 |
五、四大对象的初始化时机
5.1 Executor:SqlSession 创建时初始化
在openSessionFromDataSource中,通过configuration.newExecutor()创建。
5.2 StatementHandler:执行 SQL 时创建
// SimpleExecutor.doQuerypublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 创建 StatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// ...}}
// Configurationpublic StatementHandler newStatementHandler(Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 创建 RoutingStatementHandler(会根据 SQL 类型路由到对应的 StatementHandler)StatementHandler statementHandler = new RoutingStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);// 插件拦截statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}
5.3 ParameterHandler 和 ResultSetHandler:在 StatementHandler 创建时初始化
// RoutingStatementHandler 构造方法publicRoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 根据 MappedStatement 的类型,选择具体的 StatementHandlerswitch (ms.getStatementType()) {case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new RuntimeException("...");}}
而这些具体的 StatementHandler 的父类 BaseStatementHandler 在构造时会创建 ParameterHandler 和 ResultSetHandler
// BaseStatementHandler 构造方法protected BaseStatementHandler(Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// ...this.parameterHandler = configuration.newParameterHandler(ms, parameter, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, ms, rowBounds, parameterHandler, resultHandler, boundSql);}
总结:
对象 | 初始化时机 | 创建位置 |
Executor | openSession 时 | Configuration.newExecutor() |
StatementHandler | 执行 SQL 时 | Configuration.newStatementHandler() |
ParameterHandler | 创建 StatementHandler 时 | Configuration.newParameterHandler() |
ResultSetHandler | 创建 StatementHandler 时 | Configuration.newResultSetHandler() |
六、一级缓存与 SqlSession 的绑定
一级缓存是 SqlSession 级别的,在 Executor 中实现。回顾 BaseExecutor:
// BaseExecutorpublic abstract class BaseExecutor implements Executor {protected PerpetualCache localCache; // 一级缓存protected PerpetualCache localOutputParameterCache;// 查询时先从缓存取@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, CacheKey key, BoundSql boundSql) {// 从 localCache 中获取List<E> 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);}return list;}}
关键点:
- 一级缓存的生命周期 = SqlSession 的生命周期
- SqlSession 关闭时,一级缓存随之销毁
- 执行 update/insert/delete 会清空一级缓存
七、面试高频题
Q1:SqlSessionFactory 是线程安全的吗?SqlSession 呢?
A:
SqlSessionFactory是线程安全的,因为它只持有Configuration(不可变),多个线程可以共享同一个工厂。SqlSession不是线程安全的,每个线程应该有自己的 SqlSession,用完关闭。这也是为什么在 Web 应用中,通常一个请求对应一个 SqlSession。
Q2:openSession() 和不带参数的 openSession() 有什么区别?
A:
openSession():不自动提交,需要手动 commit。openSession(true):自动提交,每次 SQL 执行后自动 commit。openSession(ExecutorType.BATCH):批量执行器,适合批量操作。
Q3:Executor 和 StatementHandler 的区别?
A:
Executor是执行器的顶级接口,负责管理一级缓存、事务、批量操作等粗粒度的逻辑。StatementHandler负责细粒度的 JDBC Statement 操作:创建 Statement、设置参数、执行 SQL、处理结果集。- Executor 内部调用 StatementHandler 完成具体的数据库操作。
Q4:DefaultSqlSession 里的 dirty 标志位有什么用?
A:dirty表示自上次 commit/rollback 以来,是否执行过 insert/update/delete 等变更操作。在close()时,如果dirty为 true 且autoCommit为 false,则会自动回滚,防止未提交的变更被意外丢失。
Q5:SqlSession 的 close() 会不会把连接也关了?
A:会。close()会调用executor.close(),最终关闭数据库连接(连接会归还给连接池)。这也是为什么每次用完 SqlSession 都要 close 的原因。
八、下篇预告
第 5 篇我们将深入Executor 执行器三级架构,详细拆解:
- SimpleExecutor、ReuseExecutor、BatchExecutor 的实现差异
- CachingExecutor 如何用装饰器模式包装二级缓存
- 执行器的创建与选择逻辑
如果觉得有帮助,欢迎点赞、在看、转发支持!
系列持续更新,关注不走丢 👇
夜雨聆风