手写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:线程安全的守护者
SqlSessionInterceptor
是SqlSessionTemplate
实现线程安全的核心,它通过动态代理拦截所有方法调用:
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);
}
常见问题排查指南
-
Mapper接口无法注入:检查
@MapperScan
包路径配置 -
事务不生效:确认
@EnableTransactionManagement
已启用 -
连接泄漏:检查
SqlSession
是否正确关闭 -
性能问题:调整连接池参数和MyBatis缓存配置
总结
MyBatis-Spring整合通过精妙的架构设计,实现了两个框架的无缝协作:
-
@MapperScan机制:通过Spring的扩展点实现Mapper接口的自动注册
-
SqlSessionTemplate:通过动态代理和线程局部存储实现线程安全
-
事务集成:通过
TransactionSynchronizationManager
实现与Spring事务的深度集成
理解这些底层机制,不仅有助于我们更好地使用MyBatis-Spring整合,还能在遇到复杂问题时快速定位根本原因,这是构建高质量企业级应用的重要基础。
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我。文末有免费源码
💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!💖常来我家多看看,
📕网址:扣棣编程,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!
往期文章推荐:
基于Springboot + vue实现的学生宿舍信息管理系统
免费获取宠物商城源码--SpringBoot+Vue宠物商城网站系统
【2025小年源码免费送】