手写MyBatis第107弹:@MapperScan原理与SqlSessionTemplate线程安全机制

MyBatis-Spring整合深度解析:@MapperScan原理与SqlSessionTemplate线程安全机制

「MyBatis-Spring整合内核揭秘:@MapperScan自动注册原理+SqlSessionTemplate线程安全实现+完整工程搭建指南」

Spring与MyBatis整合的架构设计哲学

在企业级应用开发中,Spring框架的IoC容器和事务管理能力与MyBatis的SQL映射能力形成了完美的互补。MyBatis-Spring整合项目的核心目标就是让这两个优秀的框架能够无缝协作,发挥各自的优势。这种整合不仅仅是简单的API桥接,更是一种深度的架构融合。

目录

MyBatis-Spring整合深度解析:@MapperScan原理与SqlSessionTemplate线程安全机制

Spring与MyBatis整合的架构设计哲学

基础工程搭建:模块化设计的艺术

项目结构的最佳实践

核心依赖的精准配置

@MapperScan注解的自动注册原理深度解析

注解的元数据定义

MapperScannerRegistrar的注册机制

ClassPathMapperScanner的自定义扫描逻辑

MapperFactoryBean的代理创建机制

SqlSessionTemplate的线程安全实现机制

线程安全的设计挑战

SqlSessionTemplate的核心架构

SqlSessionInterceptor:线程安全的守护者

SqlSessionUtils的会话管理

事务集成的工作原理

完整配置示例与最佳实践

Java配置方式

事务管理的配置

性能优化与问题排查

连接池配置优化

常见问题排查指南

总结


🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥 有兴趣可以联系我。文末有免费源码

免费获取源码。

更多内容敬请期待。如有需要可以联系作者免费送

更多源码定制,项目修改,项目二开可以联系作者

点击可以进行搜索(每人免费送一套代码):千套源码目录(点我)

2025元旦源码免费送(点我)

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。

基础工程搭建:模块化设计的艺术

项目结构的最佳实践

一个良好的MyBatis-Spring整合项目应该遵循清晰的分层架构:

XML 复制代码
 mybatis-spring-demo/
 ├── src/
 │   ├── main/
 │   │   ├── java/
 │   │   │   └── com/
 │   │   │       └── example/
 │   │   │           ├── config/           # 配置类
 │   │   │           ├── entity/           # 实体类
 │   │   │           ├── mapper/           # Mapper接口
 │   │   │           ├── service/          # 业务服务层
 │   │   │           └── Application.java  # 启动类
 │   │   └── resources/
 │   │       ├── mapper/                   # MyBatis映射文件
 │   │       ├── application.yml           # 应用配置
 │   │       └── mybatis-config.xml        # MyBatis配置
 │   └── test/                             # 测试代码
 ├── pom.xml                              # Maven依赖配置
 └── README.md
核心依赖的精准配置

在pom.xml中,我们需要精心配置Spring和MyBatis的相关依赖:

XML 复制代码
 <dependencies>
     <!-- Spring Context 核心依赖 -->
     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context</artifactId>
         <version>5.3.20</version>
     </dependency>
     
     <!-- MyBatis Spring 整合包 -->
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis-spring</artifactId>
         <version>2.0.7</version>
     </dependency>
     
     <!-- MyBatis 核心 -->
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.5.9</version>
     </dependency>
     
     <!-- 数据库连接池 -->
     <dependency>
         <groupId>com.zaxxer</groupId>
         <artifactId>HikariCP</artifactId>
         <version>4.0.3</version>
     </dependency>
     
     <!-- 数据库驱动 -->
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.29</version>
     </dependency>
 </dependencies>

@MapperScan注解的自动注册原理深度解析

注解的元数据定义

@MapperScan注解是MyBatis-Spring整合的核心入口,它通过Spring的组件扫描机制自动注册Mapper接口:

java 复制代码
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 @Import(MapperScannerRegistrar.class)
 @Repeatable(MapperScans.class)
 public @interface MapperScan {
     
     // 指定要扫描的包路径
     String[] value() default {};
     
     // 指定要扫描的包名
     String[] basePackages() default {};
     
     // 指定基础包类
     Class<?>[] basePackageClasses() default {};
     
     // 指定Bean名称生成器
     Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
     
     // 指定注解过滤器
     Class<? extends Annotation> annotationClass() default Annotation.class;
     
     // 指定标记接口
     Class<?> markerInterface() default Class.class;
     
     // SQL会话模板引用
     String sqlSessionTemplateRef() default "";
     
     // SQL会话工厂引用
     String sqlSessionFactoryRef() default "";
     
     // Mapper工厂Bean类
     Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
 }

MapperScannerRegistrar的注册机制

@MapperScan通过@Import注解引入了MapperScannerRegistrar,这是Spring框架中处理@Import注解的标准扩展点:

java 复制代码
 public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
     
     private ResourceLoader resourceLoader;
     
     @Override
     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
                                       BeanDefinitionRegistry registry) {
         // 获取@MapperScan注解的属性
         AnnotationAttributes mapperScanAttrs = AnnotationAttributes
             .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
         
         if (mapperScanAttrs != null) {
             // 注册Mapper扫描器
             registerBeanDefinitions(mapperScanAttrs, registry);
         }
     }
     
     private void registerBeanDefinitions(AnnotationAttributes annoAttrs, 
                                        BeanDefinitionRegistry registry) {
         // 创建ClassPathMapperScanner
         ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
         
         // 配置扫描器参数
         scanner.setAnnotationClass(annoAttrs.getClass("annotationClass"));
         scanner.setMarkerInterface(annoAttrs.getClass("markerInterface"));
         scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
         scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
         scanner.setMapperFactoryBeanClass(annoAttrs.getClass("factoryBean"));
         
         // 注册过滤器
         scanner.registerFilters();
         
         // 执行扫描
         scanner.scan(annoAttrs.getStringArray("value"));
         scanner.scan(annoAttrs.getStringArray("basePackages"));
     }
     
     @Override
     public void setResourceLoader(ResourceLoader resourceLoader) {
         this.resourceLoader = resourceLoader;
     }
 }

ClassPathMapperScanner的自定义扫描逻辑

ClassPathMapperScanner继承自Spring的ClassPathBeanDefinitionScanner,重写了扫描逻辑以适配MyBatis的特殊需求:

java 复制代码
 public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
     
     private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
     private String sqlSessionFactoryBeanName;
     private String sqlSessionTemplateBeanName;
     
     @Override
     public Set<BeanDefinitionHolder> doScan(String... basePackages) {
         // 调用父类扫描方法获取候选Bean定义
         Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
         
         if (beanDefinitions.isEmpty()) {
             logger.warn("No MyBatis mapper was found in '" + 
                        Arrays.toString(basePackages) + "' package. Please check your configuration.");
         } else {
             // 处理扫描到的Bean定义
             processBeanDefinitions(beanDefinitions);
         }
         
         return beanDefinitions;
     }
     
     private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
         for (BeanDefinitionHolder holder : beanDefinitions) {
             GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
             
             // 设置Mapper工厂Bean类
             definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
             definition.setBeanClass(this.mapperFactoryBeanClass);
             
             // 设置SQL会话工厂引用
             if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
                 definition.getPropertyValues().add("sqlSessionFactory", 
                     new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
             }
             
             // 设置SQL会话模板引用
             if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                 definition.getPropertyValues().add("sqlSessionTemplate", 
                     new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
             }
         }
     }
 }

MapperFactoryBean的代理创建机制

MapperFactoryBean是Spring FactoryBean的实现,负责创建Mapper接口的代理实例:

java 复制代码
 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
     protected void checkDaoConfig() {
         super.checkDaoConfig();
         
         // 验证Mapper接口配置
         if (this.mapperInterface == null) {
             throw new IllegalArgumentException("Property 'mapperInterface' is required");
         }
         
         // 将Mapper接口注册到MyBatis配置中
         if (this.addToConfig && !getSqlSession().getConfiguration().hasMapper(this.mapperInterface)) {
             try {
                 getSqlSession().getConfiguration().addMapper(this.mapperInterface);
             } catch (Exception e) {
                 throw new IllegalArgumentException(e);
             }
         }
     }
     
     @Override
     public T getObject() throws Exception {
         // 从SQL会话中获取Mapper代理实例
         return getSqlSession().getMapper(this.mapperInterface);
     }
     
     @Override
     public Class<T> getObjectType() {
         return this.mapperInterface;
     }
     
     @Override
     public boolean isSingleton() {
         return true;
     }
 }

SqlSessionTemplate的线程安全实现机制

线程安全的设计挑战

在传统的MyBatis使用中,SqlSession实例不是线程安全的,这意味着在Web应用等多线程环境中,每个请求都需要创建新的SqlSession实例。SqlSessionTemplate通过巧妙的包装设计解决了这个问题。

SqlSessionTemplate的核心架构

java 复制代码
 public class SqlSessionTemplate implements SqlSession, DisposableBean {
     
     private final SqlSessionFactory sqlSessionFactory;
     private final ExecutorType executorType;
     private final SqlSession sqlSessionProxy;
     private final PersistenceExceptionTranslator exceptionTranslator;
     
     public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
         this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
     }
     
     public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
         this(sqlSessionFactory, executorType, 
              new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment(), true));
     }
     
     public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                              PersistenceExceptionTranslator exceptionTranslator) {
         this.sqlSessionFactory = sqlSessionFactory;
         this.executorType = executorType;
         this.exceptionTranslator = exceptionTranslator;
         
         // 创建动态代理,这是线程安全的关键
         this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
             SqlSessionFactory.class.getClassLoader(),
             new Class[] { SqlSession.class },
             new SqlSessionInterceptor());
     }
 }

SqlSessionInterceptor:线程安全的守护者

SqlSessionInterceptorSqlSessionTemplate实现线程安全的核心,它通过动态代理拦截所有方法调用:

java 复制代码
 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);
             
             // 如果当前没有事务,立即提交
             if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                 sqlSession.commit(true);
             }
             
             return result;
         } catch (Throwable t) {
             // 异常转换:将MyBatis异常转换为Spring的DataAccessException
             Throwable unwrapped = unwrapThrowable(t);
             if (SqlSessionTemplate.this.exceptionTranslator != null && 
                 unwrapped instanceof PersistenceException) {
                 throw SqlSessionTemplate.this.exceptionTranslator
                     .translateExceptionIfPossible((PersistenceException) unwrapped);
             } else {
                 throw unwrapped;
             }
         } finally {
             // 关闭SqlSession(如果不在事务中)
             closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
         }
     }
 }

SqlSessionUtils的会话管理

SqlSessionUtils负责SqlSession与Spring事务的集成管理:

java 复制代码
public abstract class SqlSessionUtils {
    
    private static final String NO_EXECUTOR_TYPE_SPECIFIED = "No ExecutorType specified";
    private static final String NO_SQL_SESSION_FACTORY_SPECIFIED = "No SqlSessionFactory specified";
    
    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, 
                                          ExecutorType executorType,
                                          PersistenceExceptionTranslator exceptionTranslator) {
        
        // 参数验证
        if (sessionFactory == null) {
            throw new IllegalArgumentException(NO_SQL_SESSION_FACTORY_SPECIFIED);
        }
        
        // 尝试从当前事务中获取SqlSession
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
            .getResource(sessionFactory);
        
        if (holder != null && holder.isSynchronizedWithTransaction()) {
            if (holder.getExecutorType() != executorType) {
                throw new TransientDataAccessResourceException(
                    "Cannot change ExecutorType when there is an existing transaction");
            }
            
            // 增加引用计数
            holder.requested();
            return holder.getSqlSession();
        }
        
        // 创建新的SqlSession
        SqlSession session = sessionFactory.openSession(executorType);
        
        // 如果当前有事务,注册到事务同步管理器
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            // 创建SqlSessionHolder
            SqlSessionHolder holderToRegister = new SqlSessionHolder(session, executorType, exceptionTranslator);
            TransactionSynchronizationManager.bindResource(sessionFactory, holderToRegister);
            
            // 注册事务同步
            holderToRegister.setSynchronizedWithTransaction(true);
            if (holderToRegister.isSynchronizedWithTransaction()) {
                holderToRegister.requested();
            }
        }
        
        return session;
    }
    
    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
        // 如果session为null或已关闭,直接返回
        if (session == null || session instanceof SqlSessionTemplate) {
            return;
        }
        
        // 检查session是否与当前事务绑定
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
            .getResource(sessionFactory);
        
        if (holder != null && holder.getSqlSession() == session) {
            // 减少引用计数,但不立即关闭
            holder.released();
        } else {
            // 不在事务中,立即关闭session
            session.close();
        }
    }
}

事务集成的工作原理

SqlSessionTemplate与Spring事务管理的集成是通过TransactionSynchronizationManager实现的:

java 复制代码
public abstract class TransactionSynchronizationManager {
    
    private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<>("Transactional resources");
    
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
        new NamedThreadLocal<>("Transaction synchronizations");
    
    private static final ThreadLocal<String> currentTransactionName =
        new NamedThreadLocal<>("Current transaction name");
    
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
        new NamedThreadLocal<>("Current transaction read-only status");
    
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
        new NamedThreadLocal<>("Current transaction isolation level");
    
    private static final ThreadLocal<Boolean> actualTransactionActive =
        new NamedThreadLocal<>("Actual transaction active");
}

完整配置示例与最佳实践

Java配置方式

java 复制代码
@Configuration
@MapperScan(basePackages = "com.example.mapper", 
           sqlSessionFactoryRef = "sqlSessionFactory")
public class MyBatisConfig {
    
    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        return dataSource;
    }
    
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/*.xml"));
        return sessionFactory.getObject();
    }
    
    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }
    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

事务管理的配置

java 复制代码
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    
    @Bean
    public TransactionInterceptor transactionInterceptor(PlatformTransactionManager transactionManager) {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionManager(transactionManager);
        
        // 配置事务属性
        Properties properties = new Properties();
        properties.setProperty("get*", "PROPAGATION_REQUIRED,readOnly");
        properties.setProperty("find*", "PROPAGATION_REQUIRED,readOnly");
        properties.setProperty("select*", "PROPAGATION_REQUIRED,readOnly");
        properties.setProperty("save*", "PROPAGATION_REQUIRED,-Exception");
        properties.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");
        properties.setProperty("delete*", "PROPAGATION_REQUIRED,-Exception");
        
        interceptor.setTransactionAttributes(properties);
        return interceptor;
    }
}

性能优化与问题排查

连接池配置优化

java 复制代码
@Bean
public DataSource dataSource() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    config.setUsername("root");
    config.setPassword("password");
    config.setMaximumPoolSize(20);
    config.setMinimumIdle(5);
    config.setConnectionTimeout(30000);
    config.setIdleTimeout(600000);
    config.setMaxLifetime(1800000);
    config.setAutoCommit(false);
    return new HikariDataSource(config);
}

常见问题排查指南

  1. Mapper接口无法注入 :检查@MapperScan包路径配置

  2. 事务不生效 :确认@EnableTransactionManagement已启用

  3. 连接泄漏 :检查SqlSession是否正确关闭

  4. 性能问题:调整连接池参数和MyBatis缓存配置

总结

MyBatis-Spring整合通过精妙的架构设计,实现了两个框架的无缝协作:

  1. @MapperScan机制:通过Spring的扩展点实现Mapper接口的自动注册

  2. SqlSessionTemplate:通过动态代理和线程局部存储实现线程安全

  3. 事务集成 :通过TransactionSynchronizationManager实现与Spring事务的深度集成

理解这些底层机制,不仅有助于我们更好地使用MyBatis-Spring整合,还能在遇到复杂问题时快速定位根本原因,这是构建高质量企业级应用的重要基础。

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥 有兴趣可以联系我。文末有免费源码

💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕网址:
扣棣编程** ,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!**

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

往期文章推荐:

基于Springboot + vue实现的学生宿舍信息管理系统
免费获取宠物商城源码--SpringBoot+Vue宠物商城网站系统
【2025小年源码免费送】

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

相关推荐
葫芦和十三2 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp3 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑3 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯4 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan6 小时前
多Agent之间的区别
后端
青石路8 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充8 小时前
1.面向对象设计思想
后端
IT_陈寒9 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro9 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗9 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端