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管理

相关推荐
哪里不会点哪里.10 小时前
什么是 Spring Cloud?
后端·spring·spring cloud
山上三树10 小时前
详细介绍读写锁
开发语言·c++·spring
树码小子10 小时前
Spring框架:Spring程序快速上手
java·后端·spring
老毛肚10 小时前
uniapp-ruoyi-spring部署宝塔
java·spring·uni-app
手握风云-11 小时前
JavaEE 进阶第十三期:Spring Ioc & DI,从会用容器到成为容器(下)
java·spring·java-ee
人道领域11 小时前
javaWeb从入门到进阶(SpringBoot基础案例2)
java·开发语言·mybatis
九皇叔叔12 小时前
【06】SpringBoot3 MybatisPlus 修改(Mapper)
java·spring boot·mybatis·mybatisplus
如果'\'真能转义说12 小时前
Spring 概述
java·spring
mc_故事与你12 小时前
前后端分离项目(springboot+vue+mybatis)-教学文档(SpringBoot3+Vue2)-4 (正在编写)
vue.js·spring boot·mybatis
秃头续命码农人12 小时前
谈谈对Spring、Spring MVC、SpringBoot、SpringCloud,Mybatis框架的理解
java·spring boot·spring·mvc·maven·mybatis