一、Spring整合Mybatis的核心思想
1.1 整合的本质
Spring整合第三方框架的核心思想是:将第三方框架产生的对象放到Spring容器中,使其成为Bean。对于Mybatis而言,整合的目标就是将Mybatis的核心对象(如SqlSessionFactory、Mapper接口代理对象等)交给Spring管理。
1.2 独立使用Mybatis vs Spring整合
独立使用Mybatis:
// 1. 加载配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取Mapper接口代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 5. 执行SQL
User user = userMapper.selectById(1);
Spring整合Mybatis后:
@Service
public class UserService {
@Autowired // 直接注入Mapper接口
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.selectById(id); // 直接使用
}
}
整合后,开发者无需手动创建和管理Mybatis对象,全部由Spring容器负责。
二、Mybatis-Spring 1.3.2版本源码执行流程
2.1 核心流程总览

2.2 详细源码解析
2.2.1 @MapperScan注解的入口
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MapperScannerRegistrar.class) // 关键:导入注册器
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
// ... 其他属性
}
2.2.2 MapperScannerRegistrar:BeanDefinition注册器
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 1. 获取@MapperScan注解属性
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 2. 注册扫描器
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
// 3. 创建ClassPathMapperScanner
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 4. 设置扫描包路径
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.asList(annoAttrs.getStringArray("value")));
basePackages.addAll(Arrays.asList(annoAttrs.getStringArray("basePackages")));
// 5. 设置扫描过滤器(只扫描接口)
scanner.registerFilters();
// 6. 执行扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
2.2.3 ClassPathMapperScanner:自定义扫描器
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 关键:只将接口视为候选组件
return beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata().isIndependent();
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 1. 调用父类扫描
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found...");
} else {
// 2. 处理扫描到的BeanDefinition
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 3. 关键修改:将BeanClass改为MapperFactoryBean
definition.getConstructorArgumentValues()
.addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
// 4. 设置自动装配模式为byType
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 5. 设置其他属性
definition.getPropertyValues().add("addToConfig", this.addToConfig);
}
}
}
2.2.4 MapperFactoryBean:Mapper的工厂Bean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport
implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
// 关键:获取Mapper接口的代理对象
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
// setter方法,Spring会自动调用(因为设置了AUTOWIRE_BY_TYPE)
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
super.setSqlSessionTemplate(sqlSessionTemplate);
}
}
2.2.5 SqlSessionTemplate:Spring管理的SqlSession
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.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
// 创建JDK动态代理,拦截所有SqlSession方法
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
@Override
public <T> T getMapper(Class<T> type) {
// 关键:从Configuration中获取Mapper
return getConfiguration().getMapper(type, this);
}
// 内部拦截器,负责事务管理和连接管理
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取SqlSession(可能从事务中获取,也可能新建)
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行目标方法
Object result = method.invoke(sqlSession, args);
// 如果不是事务管理的SqlSession,则提交
if (!isSqlSessionTransactional(sqlSession,
SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 异常处理...
} finally {
// 关闭SqlSession(如果不是事务管理的)
closeSqlSession(sqlSession,
SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
三、Mybatis-Spring 2.0.6版本源码执行流程
3.1 核心变化
2.0.6版本引入了MapperScannerConfigurer,提供了更灵活的配置方式:

3.2 MapperScannerConfigurer的实现
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean,
ApplicationContextAware, BeanNameAware {
private String basePackage;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 1. 创建ClassPathMapperScanner
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 2. 设置各种属性
scanner.setResourceLoader(this.applicationContext);
// 3. 注册过滤器
scanner.registerFilters();
// 4. 执行扫描
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITER));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 可以空实现
}
// 两种配置方式
// 方式1:通过@Bean定义
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.example.mapper");
return configurer;
}
// 方式2:通过@MapperScan注解(底层还是会创建MapperScannerConfigurer)
@Configuration
@MapperScan("com.example.mapper")
public class MybatisConfig {
}
}
3.3 版本对比
| 特性 | 1.3.2版本 | 2.0.6版本 |
|---|---|---|
| 核心机制 | ImportBeanDefinitionRegistrar | BeanDefinitionRegistryPostProcessor |
| 配置方式 | 只能通过@MapperScan | 支持@MapperScan和@Bean两种方式 |
| 灵活性 | 较低 | 更高,可直接在代码中配置 |
| 向后兼容 | 是 | 是,兼容1.3.2的配置方式 |
四、Spring整合Mybatis后的SQL执行流程
4.1 完整执行链路
调用Mapper方法 ↓ Mapper接口代理对象(JDK动态代理) ↓ MapperProxy.invoke() ↓ MapperMethod.execute() ↓ SqlSessionTemplate.selectOne/insert/update/delete() ↓ SqlSessionInterceptor.invoke() → 获取SqlSession ↓ 从TransactionSynchronizationManager获取或新建SqlSession ↓ 执行真正的SqlSession操作 ↓ 提交/回滚事务 ↓ 返回结果
4.2 关键源码解析
4.2.1 MapperProxy:Mapper接口的代理类
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 1. Object方法直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 2. 默认方法(Java 8+)
if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 3. 缓存或创建MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 4. 执行SQL
return mapperMethod.execute(sqlSession, args);
}
}
4.2.2 MapperMethod:SQL执行的核心
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据SQL命令类型执行不同的操作
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// 使用ResultHandler处理结果
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 返回集合
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 返回Map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 返回Cursor
result = executeForCursor(sqlSession, args);
} else {
// 返回单个对象
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
}
五、Spring整合Mybatis后的一级缓存问题
5.1 一级缓存机制回顾
Mybatis的一级缓存是基于SqlSession的缓存,默认开启。在同一SqlSession中执行相同的查询,第二次会从缓存中获取结果。
// Mybatis原生使用,一级缓存生效
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);
// 第一次查询,访问数据库
User user1 = mapper1.selectById(1);
// 第二次查询,从一级缓存获取
User user2 = mapper2.selectById(1); // 不会访问数据库
5.2 Spring整合后的问题
在Spring整合Mybatis后,SqlSession的生命周期由Spring管理,行为发生了变化:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 没有@Transactional注解
public User getUserTwice(Long id) {
// 第一次查询
User user1 = userMapper.selectById(id); // 新建SqlSession1,执行SQL
// 第二次查询
User user2 = userMapper.selectById(id); // 新建SqlSession2,执行SQL
return user2;
}
}
问题现象:两次查询都访问了数据库,一级缓存"失效"。
5.3 问题根源分析
问题的核心在于SqlSessionTemplate中的SqlSessionInterceptor:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 关键:获取SqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
// 执行SQL...
}
}
getSqlSession()方法的逻辑:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
// 1. 尝试从当前事务中获取SqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
.getResource(sessionFactory);
if (holder != null) {
// 事务中存在SqlSession,直接使用
return holder.getSqlSession();
}
// 2. 没有事务,创建新的SqlSession
return sessionFactory.openSession(executorType);
}
5.4 解决方案与理解
5.4.1 开启Spring事务
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional // 开启事务
public User getUserTwice(Long id) {
// 第一次查询
User user1 = userMapper.selectById(id); // 使用事务SqlSession
// 第二次查询
User user2 = userMapper.selectById(id); // 使用同一个SqlSession,缓存生效
return user2;
}
}
执行流程:
-
进入
@Transactional方法,Spring开启事务 -
TransactionSynchronizationManager绑定SqlSessionHolder -
所有Mapper操作使用同一个
SqlSession -
一级缓存生效
-
方法结束,提交事务,关闭
SqlSession
5.4.2 正确理解"缓存失效"
实际上,Spring整合Mybatis后的一级缓存并不是"失效",而是生命周期变短:
| 场景 | SqlSession生命周期 | 一级缓存效果 |
|---|---|---|
| 原生Mybatis | 手动控制,可长时间存活 | 缓存有效时间长 |
| Spring整合(无事务) | 每次SQL操作创建新的SqlSession | 缓存仅在一次SQL操作内有效 |
| Spring整合(有事务) | 整个事务期间使用同一个SqlSession | 事务内缓存有效 |
设计合理性:
-
没有事务时:每个SQL操作独立,应使用独立的
SqlSession -
有事务时:多个SQL操作属于同一事务,应使用同一个
SqlSession
5.5 二级缓存作为补充
当一级缓存无法满足需求时,可以考虑使用Mybatis的二级缓存(基于namespace级别):
<!-- 1. 开启Mybatis全局二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 2. 在Mapper XML中启用二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<!-- 3. 在Spring配置中设置缓存实现 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="cacheEnabled" value="true"/>
</bean>
</property>
</bean>
六、Spring整合Mybatis最佳实践
6.1 配置建议
@Configuration
@MapperScan(basePackages = "com.example.mapper",
sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 配置Mybatis
org.apache.ibatis.session.Configuration configuration =
new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(true);
sessionFactory.setConfiguration(configuration);
// 配置类型别名
sessionFactory.setTypeAliasesPackage("com.example.entity");
return sessionFactory.getObject();
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
6.2 事务管理建议
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 查询方法:使用只读事务,支持一级缓存
@Transactional(readOnly = true)
public User getUserWithCache(Long id) {
User user1 = userMapper.selectById(id); // 查询数据库
User user2 = userMapper.selectById(id); // 从一级缓存获取
return user2;
}
// 更新方法:使用读写事务
@Transactional
public void updateUser(User user) {
userMapper.updateById(user);
// 其他操作...
}
// 批量操作:使用REQUIRES_NEW传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void batchInsert(List<User> users) {
for (User user : users) {
userMapper.insert(user);
}
}
}
6.3 性能优化建议
-
合理使用一级缓存:
// 将多次查询放在同一事务中 @Transactional(readOnly = true) public void batchQuery(List<Long> ids) { for (Long id : ids) { // 相同ID的查询会利用一级缓存 userMapper.selectById(id); } } -
避免N+1查询问题:
// 不推荐:在循环中查询 public List<OrderDetail> getOrderDetailsBad(List<Long> orderIds) { List<OrderDetail> details = new ArrayList<>(); for (Long orderId : orderIds) { details.addAll(orderMapper.selectByOrderId(orderId)); // N+1查询 } return details; } // 推荐:一次性查询 public List<OrderDetail> getOrderDetailsGood(List<Long> orderIds) { return orderMapper.selectByOrderIds(orderIds); // 1次查询 } -
合理分页:
@Select("SELECT * FROM user WHERE name LIKE #{name} LIMIT #{offset}, #{limit}") List<User> selectByName(@Param("name") String name, @Param("offset") int offset, @Param("limit") int limit);
七、总结
Spring整合Mybatis的核心机制可以总结为以下几点:
-
接口扫描机制 :通过
@MapperScan或MapperScannerConfigurer扫描Mapper接口 -
BeanDefinition转换 :将接口的BeanDefinition转换为
MapperFactoryBean -
动态代理创建 :
MapperFactoryBean通过SqlSessionTemplate创建Mapper接口的代理对象 -
SQL执行拦截 :
SqlSessionTemplate通过拦截器管理SqlSession的生命周期和事务 -
事务集成:与Spring事务深度集成,支持不同的事务传播行为
关键设计思想:
-
依赖注入:将Mybatis对象作为Spring Bean管理
-
AOP思想:通过动态代理拦截Mapper方法调用
-
模板模式 :
SqlSessionTemplate封装了SqlSession的复杂管理逻辑 -
资源管理 :通过
TransactionSynchronizationManager管理事务资源
理解难点:
-
为什么Mapper接口不需要实现类? → 动态代理生成实现
-
为什么可以自动注入Mapper接口? →
MapperFactoryBean创建代理对象 -
一级缓存为什么"失效"? →
SqlSession生命周期由Spring管理