Spring整合Mybatis底层源码深度解析:从@MapperScan到SQL执行的完整流程

一、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;
    }
}

执行流程

  1. 进入@Transactional方法,Spring开启事务

  2. TransactionSynchronizationManager绑定SqlSessionHolder

  3. 所有Mapper操作使用同一个SqlSession

  4. 一级缓存生效

  5. 方法结束,提交事务,关闭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 性能优化建议

  1. 合理使用一级缓存

    复制代码
    // 将多次查询放在同一事务中
    @Transactional(readOnly = true)
    public void batchQuery(List<Long> ids) {
        for (Long id : ids) {
            // 相同ID的查询会利用一级缓存
            userMapper.selectById(id);
        }
    }
  2. 避免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次查询
    }
  3. 合理分页

    复制代码
    @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的核心机制可以总结为以下几点:

  1. 接口扫描机制 :通过@MapperScanMapperScannerConfigurer扫描Mapper接口

  2. BeanDefinition转换 :将接口的BeanDefinition转换为MapperFactoryBean

  3. 动态代理创建MapperFactoryBean通过SqlSessionTemplate创建Mapper接口的代理对象

  4. SQL执行拦截SqlSessionTemplate通过拦截器管理SqlSession的生命周期和事务

  5. 事务集成:与Spring事务深度集成,支持不同的事务传播行为

关键设计思想

  • 依赖注入:将Mybatis对象作为Spring Bean管理

  • AOP思想:通过动态代理拦截Mapper方法调用

  • 模板模式SqlSessionTemplate封装了SqlSession的复杂管理逻辑

  • 资源管理 :通过TransactionSynchronizationManager管理事务资源

理解难点

  1. 为什么Mapper接口不需要实现类? → 动态代理生成实现

  2. 为什么可以自动注入Mapper接口? → MapperFactoryBean创建代理对象

  3. 一级缓存为什么"失效"? → SqlSession生命周期由Spring管理

相关推荐
BUG指挥官12 小时前
Sa-Token和Spring Security对比
java·后端·spring
geminigoth13 小时前
Spring AI Alibaba 入门开发一(备份)
java·人工智能·spring
xbgRS15 小时前
spring整合mybaits
java·spring·mybatis
ruleslol15 小时前
静态依赖注入 VS 动态依赖查询
spring
墨雨晨曦8818 小时前
Spring AI总结
java·人工智能·spring
用户31268748772020 小时前
Spring Boot 异常处理到底怎么玩的?从 DispatcherServlet 到全局兜底的全链路拆解
spring
不才不才不不才20 小时前
Spring 源码系列(17): HandlerMapping 与 HandlerAdapter 两大体系
java·后端·spring
(轻舟已过万重山)21 小时前
第40章 Spring AI 实战:企业级 AI 应用架构
人工智能·spring·架构
vHelios1 天前
【电商项目】商品服务模块的问题解决与代码逻辑思考
java·sql·mybatis
敲个大西瓜1 天前
Mybatis核心面试题
mybatis