一、为什么需要多数据源?
多数据源在以下场景中非常有用:
数据库分库分表:业务数据分布在不同的数据库实例中
读写分离:主库处理写操作,从库处理读操作
多租户系统:每个租户使用独立的数据源
异构数据库:同时使用 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_db
username: admin
password: password
driver-class-name: org.postgresql.Driver
slave:
url: jdbc:mysql://localhost:3306/log_db
username: log_user
password: log_password
driver-class-name: com.mysql.cj.jdbc.Driver
report:
url: jdbc:postgresql://localhost:5432/report_db
username: report_user
password: report_password
driver-class-name: org.postgresql.Driver
# 连接池配置
hikari:
max-pool-size: 20
min-idle: 5
connection-timeout: 30000
idle-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")
@Transactional
public 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
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource())
.packages("com.example.entity.primary")
.persistenceUnit("primary")
.properties(jpaProperties())
.build();
}
@Bean
@Primary
public 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();
}
@Bean
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(secondaryDataSource())
.packages("com.example.entity.secondary")
.persistenceUnit("secondary")
.properties(jpaProperties())
.build();
}
@Bean
public 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();
}
@Override
protected 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();
}
@Bean
public 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";
}