Spring Boot 多数据源配置
一、为什么需要多数据源?
多数据源在以下场景中非常有用:
数据库分库分表:业务数据分布在不同的数据库实例中
读写分离:主库处理写操作,从库处理读操作
多租户系统:每个租户使用独立的数据源
异构数据库:同时使用 MySQL、PostgreSQL、Oracle 等不同数据库
数据迁移和同步:需要同时访问新旧两个数据库系统
二、基础依赖配置
首先在 pom.xml 中添加必要的依赖:
<dependencies><!-- Spring Boot Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 数据访问相关 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- 多数据源动态路由 --><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version></dependency><!-- 数据库驱动 --><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><scope>runtime</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency>
</dependencies>
三、方案一:基于 dynamic-datasource 的配置(推荐)
3.1 配置文件设置
spring:datasource:dynamic:primary: master # 设置默认数据源strict: true # 严格匹配模式datasource:master:url: jdbc:postgresql://localhost:5432/main_dbusername: adminpassword: passworddriver-class-name: org.postgresql.Driverslave:url: jdbc:mysql://localhost:3306/log_dbusername: log_userpassword: log_passworddriver-class-name: com.mysql.cj.jdbc.Driverreport:url: jdbc:postgresql://localhost:5432/report_dbusername: report_userpassword: report_passworddriver-class-name: org.postgresql.Driver# 连接池配置hikari:max-pool-size: 20min-idle: 5connection-timeout: 30000idle-timeout: 600000
3.2 使用注解切换数据源
@Service
public class BusinessService {// 使用默认数据源(master)public void createUser(User user) {userRepository.save(user);}// 使用从数据源@DS("slave")public List<Log> getLogs(String userId) {return logRepository.findByUserId(userId);}// 使用报表数据源@DS("report")public Report generateReport(Date startDate, Date endDate) {return reportRepository.generateReport(startDate, endDate);}
}
3.3 事务管理
@Service
public class TransactionalService {// 单个数据源事务@Transactional@DS("master")public void updateUser(User user) {userRepository.save(user);}// 多数据源事务(需要分布式事务支持)@DS("master")@Transactionalpublic void crossDatabaseOperation(User user, Log log) {userRepository.save(user);switchToSlave();logRepository.save(log);}@DS("slave")public void switchToSlave() {// 方法级别切换数据源}
}
四、方案二:手动配置多数据源
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.example.repository.primary",entityManagerFactoryRef = "primaryEntityManagerFactory",transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfig {@Bean@Primary@ConfigurationProperties("spring.datasource.primary")public DataSource primaryDataSource() {return DataSourceBuilder.create().build();}@Bean@Primarypublic LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(EntityManagerFactoryBuilder builder) {return builder.dataSource(primaryDataSource()).packages("com.example.entity.primary").persistenceUnit("primary").properties(jpaProperties()).build();}@Bean@Primarypublic PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {return new JpaTransactionManager(entityManagerFactory);}private Map<String, Object> jpaProperties() {Map<String, Object> props = new HashMap<>();props.put("hibernate.hbm2ddl.auto", "update");props.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");return props;}
}
4.2 从数据源配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.example.repository.secondary",entityManagerFactoryRef = "secondaryEntityManagerFactory",transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.secondary")public DataSource secondaryDataSource() {return DataSourceBuilder.create().build();}@Beanpublic LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(EntityManagerFactoryBuilder builder) {return builder.dataSource(secondaryDataSource()).packages("com.example.entity.secondary").persistenceUnit("secondary").properties(jpaProperties()).build();}@Beanpublic PlatformTransactionManager secondaryTransactionManager(@Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {return new JpaTransactionManager(entityManagerFactory);}
}
五、方案三:动态数据源路由
5.1 数据源路由配置
public class DynamicDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();public static void setDataSourceKey(String dataSourceKey) {CONTEXT_HOLDER.set(dataSourceKey);}public static String getDataSourceKey() {return CONTEXT_HOLDER.get();}public static void clearDataSourceKey() {CONTEXT_HOLDER.remove();}@Overrideprotected Object determineCurrentLookupKey() {return getDataSourceKey();}
}@Configuration
public class DynamicDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("master", masterDataSource());targetDataSources.put("slave", slaveDataSource());DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(masterDataSource());return dynamicDataSource;}
}
5.2 切面编程实现自动切换
@Aspect
@Component
public class DataSourceAspect {@Before("@annotation(targetDataSource)")public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) {if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value())) {throw new RuntimeException("数据源 " + targetDataSource.value() + " 不存在");}DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value());}@After("@annotation(targetDataSource)")public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {DynamicDataSourceContextHolder.clearDataSourceKey();}
}@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {String value() default "master";
}