第43篇-MyBatis与Spring整合
面经手册 · 第43篇《MyBatis 和 Spring 怎么整合?SqlSessionTemplate 和自动装配原理》
作者:小傅哥 **博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获!😄
一、前言
实际项目中,没有人会手写 SqlSessionFactoryBuilder 来使用 MyBatis。MyBatis 几乎总是和 Spring/Spring Boot 搭配使用。 但大多数人只是照着模板写配置,面试官问一句"SqlSessionTemplate 原理是什么"、"@MapperScan 是怎么扫描 Mapper 的",就答不上来了。
二、面试题
谢飞机,小记!,面试继续。 面试官**:MyBatis 和 Spring 怎么整合的?
谢飞机:用 mybatis-spring 依赖。
面试官:整合的核心类有哪些?
谢飞机:SqlSessionFactoryBean?
面试官:SqlSessionTemplate 呢?它是线程安全的吗?
谢飞机:是线程安全的……
面试官:SqlSession 不是线程安全的吗?SqlSessionTemplate 怎么做到线程安全的?
谢飞机:代理?
面试官:Spring Boot 中 MyBatis 是怎么自动装配的?
谢飞机:starter?
面试官:具体说说我自动配置类做了什么?
谢飞机:不知道……再见!ヾ( ̄▽ ̄)
三、整合核心组件
1. mybatis-spring 关键类
┌─────────────────────────────────────────────┐
│ mybatis-spring 整合架构 │
├─────────────────────────────────────────────┤
│ SqlSessionFactoryBean │
│ → 创建 SqlSessionFactory │
│ → 读取 DataSource 和 configLocation │
│ → 扫描 Mapper XML │
├─────────────────────────────────────────────┤
│ SqlSessionTemplate │
│ → SqlSession 的线程安全代理 │
│ → 每次操作创建新 SqlSession │
│ → 自动参与 Spring 事务 │
├─────────────────────────────────────────────┤
│ MapperScannerConfigurer │
│ → 扫描 Mapper 接口 │
│ → 注册为 Spring BeanDefinition │
├─────────────────────────────────────────────┤
│ MapperFactoryBean │
│ → 为每个 Mapper 接口创建代理 Bean │
│ → 从 SqlSessionTemplate 获取 Mapper │
└─────────────────────────────────────────────┘
1**2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
2. 整合依赖
dependency>
groupId>org.mybatisgroupId>
artifactId>mybatis-springartifactId>
version>3.0.3version>
dependency>
dependency>
groupId>org.mybatis.spring.bootgroupId>
artifactId>mybatis-spring-boot-starterartifactId>
version>3.0.3version>
dependency>
1 2 3 4 5 6 7 8 9 10 11 12 13
四、SqlSessionFactoryBean
1. 配置方式
XML 配置**:
bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
property name="dataSource" ref="dataSource"/>
property name="configLocation" value="classpath:mybatis-config.xml"/>
property name="mapperLocations" value="classpath:mapper/*.xml"/>
property name="typeAliasesPackage" value="com.example.entity"/>
bean>
12 3 4 5 6 Spring Boot 配置:
mybatis:
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
1**2 3 4 5 6 7
2. 源码追踪
// org.mybatis.spring.SqlSessionFactoryBean
public class SqlSessionFactoryBean
implements FactoryBeanSqlSessionFactory>, InitializingBean, ApplicationListenerPooledConnectionAvailableEvent> {
private DataSource dataSource;
private Resource configLocation;
private Resource[] mapperLocations;
private String typeAliasesPackage;
private Configuration configuration;
@Override
public void afterPropertiesSet() throws Exception {
// 1. 构建 Configuration
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
Configuration targetConfiguration;
// 如果有 configLocation,从 XML 文件解析
if (this.configLocation != null) {
XMLConfigBuilder xmlConfigBuilder =
new XMLConfigBuilder(this.configLocation.getInputStream(), ...);
targetConfiguration = xmlConfigBuilder.parse();
}
// 否则使用空 Configuration
else {
targetConfiguration = new Configuration();
}
// 设置 DataSource
targetConfiguration.setEnvironment(
new Environment("development",
new SpringManagedTransactionFactory(), dataSource));
// 解析 mapperLocations(Mapper XML 文件)
if (this.mapperLocations != null) {
for (Resource mapperLocation : this.mapperLocations) {
XMLMapperBuilder xmlMapperBuilder =
new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, ...);
xmlMapperBuilder.parse();
}
}
return new DefaultSqlSessionFactory(targetConfiguration);
}
@Override
public SqlSessionFactory getObject() {
return this.sqlSessionFactory;
}
}
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
五、SqlSessionTemplate 原理
1. 为什么需要 SqlSessionTemplate
SqlSession 不是线程安全的(持有 Connection + 一级缓存)。在 Spring 环境中,Service 层是单例的,多个线程共享同一个 Mapper 代理,直接用 SqlSession 会出问题。 SqlSessionTemplate 就是解决线程安全的方案。
2. 核心源码
// org.mybatis.spring.SqlSessionTemplate
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy; // ← 代理对象
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor()); // ← JDK 动态代理
}
@Override
public T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
// 所有方法都委托给 sqlSessionProxy...
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
3. SqlSessionInterceptor(核心)
// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取 SqlSession(从 Spring 事务同步器或新建)
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 2. 执行方法
Object result = method.invoke(sqlSession, args);
// 3. 如果不是查询,标记为 dirty
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true); // 非事务环境下自动提交
}
return result;
} catch (Throwable t) {
// 4. 异常处理
throw t;
} finally {
// 5. 关闭 SqlSession(非事务环境下)
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
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
4. getSqlSession 详解
// org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(
SqlSessionFactory sessionFactory,
ExecutorType executorType, ...) {
// 1. 从事务同步管理器获取(事务环境下复用同一个 SqlSession)
SqlSessionHolder holder = (SqlSessionHolder)
TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session; // 事务中复用
}
// 2. 新建 SqlSession
session = sessionFactory.openSession(executorType);
// 3. 注册到事务同步管理器(事务环境下)
if (TransactionSynchronizationManager.isSynchronizationActive()) {
holder = new SqlSessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(
new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
}
return session;
}
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
5. 关键流程图解
非事务环境(每次新建):
每次调用 → getSqlSession() → 新建 SqlSession
→ 执行 SQL → 自动 commit → 自动 close
事务环境(复用):
第一次调用 → getSqlSession() → 新建 SqlSession → 绑定到 TransactionSynchronizationManager
第二次调用 → getSqlSession() → 从同步管理器获取 → 复用同一个 SqlSession
事务提交 → closeSqlSession() → 关闭
1 2 3 4 5 6 7 8
六、@MapperScan 原理
1. 配置方式
@MapperScan("com.example.mapper")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1 2 3 4 5 6 7
2. 源码追踪
// org.mybatis.spring.annotation.MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MapperScannerRegistrar.class) // ← 核心
public @interface MapperScan {
String[] basePackages() default {};
Class?>[] basePackageClasses() default {};
Class? extends Annotation> annotationClass() default Annotation.class;
Class?> markerInterface() default Class.class;
// ...
}
1 2 3 4 5 6 7 8 9 10 11
// org.mybatis.spring.mapper.MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 读取 @MapperScan 属性
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 配置扫描参数
scanner.setAnnotationClass(mapperScanAttrs.getClass("annotationClass"));
scanner.setMarkerInterface(mapperScanAttrs.getClass("markerInterface"));
// ...
// 扫描 basePackages
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// org.mybatis.spring.mapper.ClassPathMapperScanner
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
@Override
protected SetBeanDefinitionHolder> doScan(String... basePackages) {
// 1. 扫描接口
SetBeanDefinitionHolder> holders = super.doScan(basePackages);
// 2. 将每个接口注册为 MapperFactoryBean
for (BeanDefinitionHolder holder : holders) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.setBeanClass(MapperFactoryBean.class); // ← 关键
// 设置 mapperInterface
definition.getPropertyValues().add("mapperInterface",
definition.getBeanClassName());
}
return holders;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
3. MapperFactoryBean
// org.mybatis.spring.mapper.MapperFactoryBean
public class MapperFactoryBeanT> extends SqlSessionDaoSupport
implements FactoryBeanT> {
private ClassT> mapperInterface;
@Override
@SuppressWarnings("unchecked")
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
// → SqlSessionTemplate.getMapper() → Configuration.getMapper()
// → MapperRegistry.getMapper() → MapperProxyFactory.newInstance()
}
@Override
public ClassT> getObjectType() {
return this.mapperInterface;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
七、Spring Boot 自动装配
1. 自动配置类
// org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(resolveMapperLocations());
// ...
return factory.getObject();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
2. 配置项映射
mybatis:
config-location: classpath:mybatis-config.xml → SqlSessionFactoryBean.setConfigLocation()
mapper-locations: classpath:mapper/*.xml → SqlSessionFactoryBean.setMapperLocations()
type-aliases-package: com.example.entity → SqlSessionFactoryBean.setTypeAliasesPackage()
configuration:
map-underscore-to-camel-case: true → Configuration.setMapUnderscoreToCamelCase()
cache-enabled: true → Configuration.setCacheEnabled()
lazy-loading-enabled: true → Configuration.setLazyLoadingEnabled()
1 2 3 4 5 6 7 8
3. @Mapper 和 @MapperScan 区别
对比项 @Mapper @MapperScan 作用范围 接口级别 包级别 使用方式 每个接口加注解 配置类加一次 底层实现 MapperScannerConfigurer MapperScannerRegistrar 推荐 Mapper 少时 Mapper 多时(推荐)
八、常见面试追问
Q1:SqlSessionTemplate 是线程安全的吗?
是。SqlSessionTemplate 内部通过 JDK 动态代理包装了一个 SqlSession 代理,每次方法调用都从 SqlSessionInterceptor 获取新的或事务绑定的 SqlSession,方法结束后自动关闭。SqlSessionTemplate 本身是无状态的,所有操作都委托给代理对象。
Q2:MyBatis 事务和 Spring 事务的关系?
Spring 事务管理器管理 DataSource 的连接。在事务中,MyBatis 的 SqlSession 和 Spring 的数据库连接通过 TransactionSynchronizationManager 绑定到当前线程,实现事务一致性。事务提交时 Spring 提交连接并通知 MyBatis 关闭 SqlSession。
Q3:@Mapper 和 @MapperScan 能一起用吗?
可以,但不建议。同时使用时,Mapper 可能被注册两次。建议只用 @MapperScan 统一管理。
九、总结
记住三个核心要点:
1. 整合核心三个类
SqlSessionFactoryBean → 创建 SqlSessionFactory
SqlSessionTemplate → 线程安全的 SqlSession 代理
MapperScannerConfigurer / @MapperScan → 扫描 Mapper 接口
2. SqlSessionTemplate 线程安全原理
JDK 动态代理 → 每次调用获取新/复用 SqlSession
事务中:绑定到 TransactionSynchronizationManager 复用
非事务:每次新建,执行完自动关闭
3. Spring Boot 自动装配
MybatisAutoConfiguration 自动注册 SqlSessionFactory 和 SqlSessionTemplate
yaml 配置 → MybatisProperties → SqlSessionFactoryBean 属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 面试回答模板**:
MyBatis 和 Spring 的整合通过 mybatis-spring 实现,核心类有三个:SqlSessionFactoryBean 创建 SqlSessionFactory,SqlSessionTemplate 提供线程安全的 SqlSession 操作,MapperScannerConfigurer 或 @MapperScan 扫描 Mapper 接口注册为 Bean。 SqlSessionTemplate 的线程安全通过 JDK 动态代理实现。内部有一个 SqlSessionInterceptor,每次方法调用都从 SqlSessionUtils 获取 SqlSession——如果当前线程有事务绑定则复用,否则新建并在方法结束后关闭。SqlSessionTemplate 本身无状态,是线程安全的。 Spring Boot 中通过 MybatisAutoConfiguration 自动装配,根据 yaml 配置创建 SqlSessionFactory 和 SqlSessionTemplate,@MapperScan 通过 MapperScannerRegistrar 扫描接口并注册为 MapperFactoryBean,getObject() 时从 SqlSessionTemplate 获取 Mapper 代理。