手写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 小时前
log4net 简介以及简单示例(.net8)
后端
间彧3 小时前
Spring Boot分布式WebSocket实现指南:项目实战与代码详解
后端
Deschen3 小时前
设计模式-原型模式
java·设计模式·原型模式
麦麦鸡腿堡3 小时前
Java的动态绑定机制(重要)
java·开发语言·算法
时间之里3 小时前
【c++】:Lambda 表达式介绍和使用
开发语言·c++
それども3 小时前
SpringBootTest运行线程池被拒绝
java
间彧3 小时前
Spring Boot集成WebSocket项目实战详解
后端
Tiger_shl3 小时前
C# 预处理指令 (# 指令) 详解
开发语言·c#
介一安全3 小时前
【Frida Android】基础篇6:Java层Hook基础——创建类实例、方法重载、搜索运行时实例
android·java·网络安全·逆向·安全性测试·frida