当前位置: 首页 > news >正文

手写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整合项目应该遵循清晰的分层架构:

 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的相关依赖:

 <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接口:

 @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注解的标准扩展点:

 public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {private ResourceLoader resourceLoader;@Overridepublic 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) {// 创建ClassPathMapperScannerClassPathMapperScanner 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"));}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}}

ClassPathMapperScanner的自定义扫描逻辑

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

 public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;private String sqlSessionFactoryBeanName;private String sqlSessionTemplateBeanName;@Overridepublic 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接口的代理实例:

 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;}@Overrideprotected 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);}}}@Overridepublic T getObject() throws Exception {// 从SQL会话中获取Mapper代理实例return getSqlSession().getMapper(this.mapperInterface);}@Overridepublic Class<T> getObjectType() {return this.mapperInterface;}@Overridepublic boolean isSingleton() {return true;}}

SqlSessionTemplate的线程安全实现机制

线程安全的设计挑战

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

SqlSessionTemplate的核心架构

 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实现线程安全的核心,它通过动态代理拦截所有方法调用:

 private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 获取当前线程的SqlSessionSqlSession 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的DataAccessExceptionThrowable 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事务的集成管理:

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);}// 尝试从当前事务中获取SqlSessionSqlSessionHolder 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();}// 创建新的SqlSessionSqlSession session = sessionFactory.openSession(executorType);// 如果当前有事务,注册到事务同步管理器if (TransactionSynchronizationManager.isSynchronizationActive()) {// 创建SqlSessionHolderSqlSessionHolder 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 {// 不在事务中,立即关闭sessionsession.close();}}
}

事务集成的工作原理

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

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配置方式

@Configuration
@MapperScan(basePackages = "com.example.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class MyBatisConfig {@Beanpublic 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;}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource());sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));return sessionFactory.getObject();}@Beanpublic SqlSessionTemplate sqlSessionTemplate() throws Exception {return new SqlSessionTemplate(sqlSessionFactory());}@Beanpublic PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(dataSource());}
}

事务管理的配置

@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic 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;}
}

性能优化与问题排查

连接池配置优化

@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小年源码免费送】

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

http://www.dtcms.com/a/482395.html

相关文章:

  • 专家深度解析5种关键优化方法,助力品牌在AI搜索引擎中脱颖而出
  • 开发实践:基于 PHP+Uniapp 的海外版上门预约系统
  • 迁安网站建设网站没快照
  • 拜尔滤镜详细解释,原理和实践
  • XML Schema 复合类型 - 混合内容
  • C++客服端访问redis
  • 用【WPF+Dlib68】实现 侧脸 眼镜虚拟佩戴 - 用平面图表现空间视觉
  • 重庆网站优化排名上海 企业
  • 网站建设的技术需要多少钱上海软件系统开发公司
  • 汽车用颗粒物传感器:市场趋势、技术革新与行业挑战
  • HICom论文阅读
  • Spring Framework源码解析——ServletContextAware
  • 苏州微网站建设公司做镜像网站
  • OpenStack 网络实现的底层细节-PORT/TAP
  • Chrome 安装失败且提示“无可用的更新” 或 “与服务器的连接意外终止”,Chrome 离线版下载安装教程
  • 02-如何使用Chrome工具排查内存泄露问题
  • 通过不同语言建立多元认知,提升创新能力
  • Tomcat 架构解析与线程池优化策略
  • springboot在DTO使用service,怎么写
  • YOLOv1 详解:实时目标检测的开山之作
  • Vue3 + SpringBoot 分片上传与断点续传方案设计
  • CTFSHOW WEB 3
  • 做个网站费用建材营销型的网站
  • POrtSwigger靶场之CSRF where token validation depends on token being present通关秘籍
  • Java 离线视频目标检测性能优化:从 Graphics2D 到 OpenCV 原生绘图的 20 倍性能提升实战
  • 基于 Informer-BiGRUGATT-CrossAttention 的风电功率预测多模型融合架构
  • 如何做旅游网站推销免费企业信息发布平台
  • 基于RBAC模型的灵活权限控制
  • C++内存管理模板深度剖析
  • 新开的公司怎么做网站手机网站设计神器