Spring Boot 配置多数据源并手动配置事务

news/2024/12/16 17:22:24/

Spring Boot 配置多数据源并手动配置事务

  • 一、为什么多数据源需要手动配置?
  • 二、配置多数据源
    • 1. 数据源配置类 (DataSourceConfig)
    • 2. 主数据库 MyBatis 配置类 (PrimaryDbMyBatisConfig)
    • 3. 从数据库 MyBatis 配置类 (SecondaryDbMyBatisConfig)
    • 4. application.yml 配置
    • 5. 使用 @Transactional 指定事务管理
  • 三、问题说明:will not be managed by Spring
  • 四、总结


在现代企业级应用中,往往会涉及到多个数据库的使用。例如,一个应用可能需要同时操作不同的业务数据库,或分离读写操作。

Spring Boot 提供了对多数据源的支持,但在某些情况下,自动配置可能无法满足复杂的需求。特别是在事务管理方面,当需要在多个数据源间进行操作时,默认的事务管理方式可能不适用,或者无法满足更高的灵活性需求。

因此,手动配置多个数据源并支持事务管理是一个常见的解决方案。

一、为什么多数据源需要手动配置?

  1. 多数据源隔离:

    在微服务架构或者不同模块之间可能会使用不同的数据库,每个数据库有独立的数据源和事务管理。如果使用自动配置的方式,Spring Boot会自动配置一个数据源和一个事务管理器,这可能会导致不同数据源的事务冲突或共享问题。因此,需要手动配置多个数据源和多个事务管理

  2. 跨数据源的事务管理

    Spring 默认的事务管理仅适用于单一数据源,如果在一个方法中涉及到多个数据源,Spring并不会自动处理这些数据源之间的事务关系。这时,我们需要显式地配置事务管理器,以便手动控制事务的提交与回滚。

  3. 灵活的事务控制:

    有时我们可能需要在不同的数据源之间手动控制事务,比如先操作主数据源,再操作从数据源,或者在同一个方法中对多个数据源进行操作。这时,手动配置事务管理器可以为我们提供更高的灵活性和控制能力。

二、配置多数据源

1. 数据源配置类 (DataSourceConfig)

这个配置类主要负责配置 primary_db 和 secondary_db 两个数据源,并为它们设置基本的连接属性。

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {/*** 配置主数据库(primary_db)数据源* * 这个方法使用 `@ConfigurationProperties` 注解,从 `application.yml` 配置文件中读取 * `spring.datasource.primary_db` 下的配置,自动配置数据源的连接信息(如 URL、用户名、密码等)。* * @return 配置的主数据库数据源对象*/@Bean(name = "primaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.primary_db")public DataSource primaryDataSource() {return DataSourceBuilder.create().build(); // 创建并返回数据源对象}/*** 配置从数据库(secondary_db)数据源* * 该方法使用 `@ConfigurationProperties` 注解,从 `application.yml` 配置文件中读取 * `spring.datasource.secondary_db` 下的配置,自动配置数据源的连接信息。* * @return 配置的从数据库数据源对象*/@Bean(name = "secondaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.secondary_db")public DataSource secondaryDataSource() {return DataSourceBuilder.create().build(); // 创建并返回数据源对象}}
  • primaryDataSource()secondaryDataSource() 方法用于创建和配置两个数据源。通过 @ConfigurationProperties 注解,Spring 会自动读取配置文件中的数据源信息,并将其与 DataSource 配置绑定。DataSourceBuilder.create().build() 是 Spring Boot 提供的便捷方式来创建数据源实例。

  • @EnableTransactionManagement 启用了事务管理功能,允许你在需要的地方使用 @Transactional 注解来声明事务。

2. 主数据库 MyBatis 配置类 (PrimaryDbMyBatisConfig)

这个配置类负责配置与主数据库primary_db)相关的 MyBatis 组件。它包含了 MyBatis 的 SqlSessionFactory事务管理器配置。

@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = "com.cx.mail.mapper.primary_db", sqlSessionFactoryRef = "primaryDbSqlSessionFactory")
public class PrimaryDbMyBatisConfig {@Autowired@Qualifier("primaryDataSource")private DataSource primaryDataSource;/*** 配置主数据库的 MyBatis SqlSessionFactory* * 创建并配置 MyBatis 的 `SqlSessionFactory`,它是 MyBatis 用来与数据库交互的核心组件。* 在这个配置中,我们指定了使用的 `primaryDataSource` 数据源。* * @return 配置好的 SqlSessionFactory 对象* @throws Exception 可能抛出异常,例如创建 `SqlSessionFactory` 时出错*/@Beanpublic SqlSessionFactory primaryDbSqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();factory.setDataSource(primaryDataSource); // 设置使用的主数据库数据源// 配置全局设置,主要是 MetaObjectHandler,用于自动填充字段GlobalConfig globalConfig = GlobalConfigUtils.defaults();globalConfig.setMetaObjectHandler(new EntityAutoFillHandler()); // 自动填充创建时间和更新时间factory.setGlobalConfig(globalConfig);// 配置 MyBatis Plus 插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 添加分页插件factory.setPlugins(interceptor);// 配置 MyBatis 基本设置MybatisConfiguration configuration = new MybatisConfiguration();configuration.setMapUnderscoreToCamelCase(true); // 将数据库字段下划线命名转换为 Java 类的驼峰命名法configuration.setLogImpl(StdOutImpl.class); // 配置 SQL 打印factory.setConfiguration(configuration);// 配置 MyBatis Mapper XML 文件的位置factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/primary_db/*.xml"));return factory.getObject(); // 返回创建的 SqlSessionFactory}/*** 配置主数据库事务管理器* * 该方法创建一个 `DataSourceTransactionManager`,它是 Spring 提供的事务管理器,用来处理主数据库的事务。* * @param dataSource 主数据库的数据源* @return 配置好的事务管理器*/@Primary@Bean(name = "primaryDbTransactionManager")public DataSourceTransactionManager primaryDbTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource); // 返回主数据库事务管理}
}
  • primaryDbSqlSessionFactory() 方法配置了主数据库SqlSessionFactory,并指定了与该数据库交互时的 MyBatis 配置。

  • primaryDbTransactionManager() 方法创建了主数据库事务管理器(DataSourceTransactionManager),使得我们可以通过 @Transactional 注解来管理与主数据库相关的事务。

3. 从数据库 MyBatis 配置类 (SecondaryDbMyBatisConfig)

这个配置类与主数据库配置类似,但是它专门配置与从数据库(secondary_db)相关的 MyBatis 组件。

@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = "com.cx.mail.mapper.secondary_db", sqlSessionFactoryRef = "secondaryDbSqlSessionFactory")
public class SecondaryDbMyBatisConfig {@Autowired@Qualifier("secondaryDataSource")private DataSource secondaryDataSource;/*** 配置从数据库的 MyBatis SqlSessionFactory* * 创建并配置从数据库的 `SqlSessionFactory`,它将用于从数据库的 MyBatis 操作。* * @return 配置好的 SqlSessionFactory 对象* @throws Exception 可能抛出异常,例如创建 `SqlSessionFactory` 时出错*/@Bean(name = "secondaryDbSqlSessionFactory")public SqlSessionFactory secondaryDbSqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();factory.setDataSource(secondaryDataSource); // 设置使用的从数据库数据源// 配置全局设置,主要是 MetaObjectHandler,用于自动填充字段GlobalConfig globalConfig = GlobalConfigUtils.defaults();globalConfig.setMetaObjectHandler(new EntityAutoFillHandler());factory.setGlobalConfig(globalConfig);// 配置 MyBatis Plus 插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 添加分页插件factory.setPlugins(interceptor);// 配置 MyBatis 基本设置MybatisConfiguration configuration = new MybatisConfiguration();configuration.setMapUnderscoreToCamelCase(true); // 设置驼峰命名法configuration.setLogImpl(StdOutImpl.class); // 配置 SQL 打印factory.setConfiguration(configuration);// 设置 Mapper XML 文件的位置factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/secondary_db/*.xml"));return factory.getObject(); // 返回创建的 SqlSessionFactory}/*** 配置从数据库事务管理器* * 该方法创建一个 `DataSourceTransactionManager`,它是 Spring 提供的事务管理器,用来处理从数据库的事务。* * @param dataSource 从数据库的数据源* @return 配置好的事务管理器*/@Bean(name = "secondaryDbTransactionManager")public DataSourceTransactionManager secondaryDbTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource); // 返回从数据库事务管理}
}
  • secondaryDbSqlSessionFactory()secondaryDbTransactionManager() 方法与主数据库的配置类类似,但它们专门用于配置和管理从数据库的 MyBatis 和事务。

4. application.yml 配置

# 本地库
spring:# 数据源配置 mysqldatasource:primary_db:url: jdbc:mysql://localhost:3306/primary_dbusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driversecondary_db:url: jdbc:mysql://localhost:3306/secondary_dbusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
  • 配置了 primary_dbsecondary_db 两个数据源的连接信息(如 URL、用户名、密码等),这些配置将被数据源配置类读取。

5. 使用 @Transactional 指定事务管理

在多个数据源配置好之后,需要手动管理事务,确保每个数据源的操作在不同的事务上下文中进行。

使用 @Transactional 指定事务管理器,应该加在需要使用该事务管理器进行事务管理服务层方法或类 上。通过 @Transactional 注解来指定使用哪个事务管理器。这样,Spring 会使用指定的事务管理器来管理该方法的事务。

@Service
@Transactional(transactionManager = "primaryDbTransactionManager")  // 显式指定事务管理
public class AdminServiceImpl implements AdminService {@Autowiredprivate FakeDomainMapper fakeDomainMapper;@Autowiredprivate FakeUserService fakeUserService;/*** 新增域名*/@Overridepublic void addFakeDomain(String domain, String domainDescription) {String loginUsername = FakeSecurityUtil.getUsername();if (!fakeUserService.isFakeUserAdmin(loginUsername)) {throw new FakeServiceException("FORBIDDEN");}FakeDomainEntity fakeDomainEntity = new FakeDomainEntity();fakeDomainEntity.setFakeDomain(domain);if (domainDescription != null) {fakeDomainEntity.setFakeDescription(domainDescription);}fakeDomainEntity.setFakeCreated(new Date());fakeDomainEntity.setFakeModified(new Date());// 设置默认的虚拟配额String fakeDefaultSettings = "default_user_quota:2048;";fakeDomainEntity.setFakeSettings(fakeDefaultSettings);fakeDomainMapper.insertFakeDomain(fakeDomainEntity);}
}
  • @Transactional(transactionManager = "primaryDbTransactionManager") 使得 addFakeDomain 方法使用 primaryDbTransactionManager 来管理事务,这样该方法的事务操作会应用到 primaryDataSource 数据源上。

三、问题说明:will not be managed by Spring

在使用 @Transactional 注解时,如果有多个事务管理器(例如多数据源配置),需要特别注意 指定事务管理

否则,Spring 无法自动决定使用哪个事务管理器来管理当前方法的事务,可能导致事务 无法被 Spring 管理,从而出现错误或者无法保证事务的一致性。

如果没有指定正确的事务管理器,Spring 可能会抛出类似如下的错误:

在这里插入图片描述

这个问题通常出现在以下两种情况下:

  1. 多个数据源
    应用程序配置了多个数据源,每个数据源有自己的事务管理器。Spring 可能不知道应该使用哪个事务管理器来管理当前方法的事务。

  2. 未指定事务管理
    在有多个事务管理器的环境下,@Transactional 默认使用主数据源的事务管理器。如果你没有显式指定事务管理器,可能会导致事务无法被正确管理,或者使用错误的事务管理器。


四、总结

通过上述配置,系统能够实现同时操作两个不同的数据源,每个数据源有独立的事务管理、MyBatis 配置等,确保了在进行跨库操作时能够稳定运行。每个模块的配置都进行了明确的注释,方便维护和修改。

  • 在有 多个数据源多个事务管理 的情况下,必须显式指定 transactionManager,否则事务将不会被 Spring 管理,可能导致错误。

  • 使用 @Transactional(transactionManager = "primaryDbTransactionManager") 来明确指定事务管理器。

  • 默认情况下,如果没有显式指定事务管理器,Spring 只会使用主数据源的事务管理器,其他数据源的事务管理器将无法生效。

  • 通过正确的事务管理器配置,可以确保每个方法或服务层类的事务控制是正确的,从而避免由于事务管理不当导致的数据库操作异常或数据不一致问题。


http://www.ppmy.cn/news/1555630.html

相关文章

【数据分享】2013-2023年我国省市县三级的逐年CO数据(免费获取\excel\shp格式)

空气质量数据是在我们日常研究中经常使用的数据!之前我们给大家分享了2000-2023年的省市县三级的逐年PM2.5数据、2000-2023年的省市县三级的逐年PM10数据、2013-2023年的省市县三级的逐年SO2数据、2000-2023年省市县三级的逐年O3数据和2008-2023年我国省市县三级的逐…

MySQL 性能调优:打造高效数据库

SQL 语句层面的性能调优策略 合理选择字段属性 在创建 MySQL 表时,为了获取更好的性能,选择合适的字段属性至关重要。 首先,要依据实际情况合理设置字段的类型及宽度。例如,对于像手机号码这类固定长度为 11 位的字段&#xff…

SpringCloud微服务实战系列:01让SpringCloud项目在你机器上运行起来

目录 项目选型 项目安装-本地运行起来 软件安装: 项目启动: 总结&答疑 项目选型 软件开发,基本上都不会从0开始,一般都是在其他项目或者组件的基础上进行整合优化迭代,站在巨人肩膀上才能看得更远&#xff0c…

.NET MAUI开发的安卓、iOS软件和Java开发的安卓和Swift开发的iOS的区别

1. 开发语言和平台 .NET MAUI: 使用 C# 作为开发语言。通过 .NET 6/7/8 平台编译并打包应用。.NET MAUI 会根据目标平台(Android 或 iOS)编译和运行不同的本地代码。代码是跨平台的,开发者可以使用相同的代码库为 Android 和 iOS …

什么是战略思想?

古今中外,关于战略是什么?有非常多的理论,也有不同的视角。 中国最早的涉及战略的书籍据传是黄帝所著的《握奇文》,后有较为系统的兵法战略书籍为周朝姜太公(亦称姜尚、姜子牙等)所著的《六韬》&#xff0c…

关于Python程序消费Kafka消息不稳定问题的处理方法

在使用Python程序消费Kafka消息的过程中,有时会遇到各种不稳定的情况,如自动提交偏移量无效、CommitFailedError错误等。这些问题不仅影响了数据处理的可靠性,还可能导致重复消费或丢失消息。本文将针对这两个常见问题提供详细的解决方案和最…

如何实现接口继承与实现继承的区别?如何处理多态性与性能的平衡?

如何实现接口继承与实现继承的区别? 接口继承:只继承方法签名 实现继承:继承实际的方法 实现接口继承:基类只定义纯虚函数,抽象为接口(纯虚类),接口类中只保留函数声明&#xff0…

新能源汽车安全充电管理方案

摘要:近年来,随着国家碳达峰和碳中和目标的提出,国家节能减排政策实施力度的进一步加大大众的环保意识、环保理念进一步深入人心,同时根据国家战略安全需要,新能源汽车行业异军突起,发展迅猛。随着新能源汽车数量的不断…