Spring Boot 配置多数据源并手动配置事务
在现代企业级应用中,往往会涉及到多个数据库的使用。例如,一个应用可能需要同时操作不同的业务数据库,或分离读写操作。
Spring Boot 提供了对多数据源的支持,但在某些情况下,自动配置可能无法满足复杂的需求。特别是在事务管理方面,当需要在多个数据源间进行操作时,默认的事务管理方式可能不适用,或者无法满足更高的灵活性需求。
因此,手动配置多个数据源并支持事务管理是一个常见的解决方案。
一、为什么多数据源需要手动配置?
-
多数据源隔离:
在微服务架构或者不同模块之间可能会使用不同的数据库,每个数据库有独立的数据源和事务管理。如果使用自动配置的方式,Spring Boot会自动配置一个数据源和一个事务管理器,这可能会导致不同数据源的事务冲突或共享问题。因此,需要手动配置多个数据源和多个事务管理器。
-
跨数据源的事务管理:
Spring 默认的事务管理仅适用于单一数据源
,如果在一个方法中涉及到多个数据源,Spring并不会自动处理这些数据源之间的事务关系。这时,我们需要显式地配置事务管理器,以便手动控制事务的提交与回滚。 -
灵活的事务控制:
有时我们可能需要在不同的数据源之间手动控制事务,比如先操作主数据源,再操作从数据源,或者在同一个方法中对多个数据源进行操作。这时,手动配置事务管理器可以为我们提供更高的灵活性和控制能力。
二、配置多数据源
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_db
和secondary_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 可能会抛出类似如下的错误:
这个问题通常出现在以下两种情况下:
-
多个数据源:
应用程序配置了多个数据源,每个数据源有自己的事务管理器。Spring 可能不知道应该使用哪个事务管理器来管理当前方法的事务。 -
未指定事务管理器:
在有多个事务管理器的环境下,@Transactional
默认使用主数据源的事务管理器。如果你没有显式指定事务管理器,可能会导致事务无法被正确管理,或者使用错误的事务管理器。
四、总结
通过上述配置,系统能够实现同时操作两个不同的数据源,每个数据源有独立的事务管理、MyBatis 配置等,确保了在进行跨库操作时能够稳定运行。每个模块的配置都进行了明确的注释,方便维护和修改。