一、核心概念
在企业级应用中,一个应用可能需要访问多个不同的数据库。这些数据库可能位于不同的服务器、使用不同的数据库类型(如 MySQL 和 PostgreSQL),或者用于读写分离、业务隔离等场景。Spring Boot 通过 DataSource
配置和 @Primary
注解,可以灵活地管理多个数据源。
关键概念
DataSource
:- 核心接口,代表一个数据库连接池。每个数据源都需要配置一个独立的
DataSource
Bean。 - Spring Boot 通常会自动配置一个
DataSource
Bean(默认为@Primary
),但在多数据源场景下,需要手动配置所有DataSource
,并明确指定哪个是主数据源。
- 核心接口,代表一个数据库连接池。每个数据源都需要配置一个独立的
@Primary
:- 当存在多个相同类型的 Bean 时,
@Autowired
默认会报错(NoUniqueBeanDefinitionException)。@Primary
注解用于标记一个 Bean 为首选 Bean,当没有明确指定注入哪个时,优先注入被@Primary
标记的 Bean。 - 在多数据源配置中,必须为一个
DataSource
添加@Primary
注解,以解决自动装配的歧义。
- 当存在多个相同类型的 Bean 时,
JdbcTemplate
/NamedParameterJdbcTemplate
:- 这些模板类依赖于
DataSource
。在多数据源环境下,需要为每个DataSource
创建对应的JdbcTemplate
Bean。 - 通过构造函数注入特定的
DataSource
来创建。
- 这些模板类依赖于
EntityManagerFactory
/TransactionManager
(JPA/Hibernate):- 如果使用 JPA,每个数据源通常需要独立的
LocalContainerEntityManagerFactoryBean
(负责实体管理) 和PlatformTransactionManager
(负责事务管理)。 - 需要通过
@Qualifier
注解明确指定使用哪个DataSource
和TransactionManager
。
- 如果使用 JPA,每个数据源通常需要独立的
@Qualifier
:- 当存在多个同类型 Bean 且没有
@Primary
,或者需要注入非主 Bean 时,使用@Qualifier
注解通过 Bean 名称来精确指定注入哪一个。 - Bean 名称通常是方法名(在
@Bean
方法上)或类名(在@Component
类上)。
- 当存在多个同类型 Bean 且没有
配置隔离:
- 通常将不同数据源的配置(
DataSource
,JdbcTemplate
,EntityManagerFactory
,TransactionManager
)放在独立的配置类中,以保持代码清晰和可维护性。
- 通常将不同数据源的配置(
读写分离:
- 一种常见的多数据源应用模式。配置一个主库(Master,用于写操作)和一个或多个从库(Slave/Read Replica,用于读操作)。
- 可以通过 AOP 切面、自定义
AbstractRoutingDataSource
或使用 ShardingSphere 等中间件实现自动路由。
二、操作步骤(非常详细)
场景设定
配置两个数据源:
- 主数据源 (primary): MySQL 数据库
db_primary
,用于核心业务。 - 次数据源 (secondary): MySQL 数据库
db_secondary
,用于日志或报表。
步骤 1:添加依赖
pom.xml
:
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot JDBC Starter (如果使用 JdbcTemplate) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- Spring Boot Data JPA Starter (如果使用 JPA) -->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> -->
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- (可选) Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
- 注意: 根据实际需求选择使用
spring-boot-starter-jdbc
(JdbcTemplate) 或spring-boot-starter-data-jpa
(JPA/Hibernate)。
步骤 2:配置文件 (application.yml
)
# application.yml
spring:
# 主数据源配置 (primary)
datasource:
primary:
url: jdbc:mysql://localhost:3306/db_primary?useSSL=false&serverTimezone=UTC
username: primary_user
password: primary_password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: PrimaryHikariCP
maximum-pool-size: 15
# 次数据源配置 (secondary)
secondary:
url: jdbc:mysql://localhost:3306/db_secondary?useSSL=false&serverTimezone=UTC
username: secondary_user
password: secondary_password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: SecondaryHikariCP
maximum-pool-size: 10
# (可选) JPA 配置 (如果使用 JPA)
# jpa:
# show-sql: true
# hibernate:
# ddl-auto: update
# properties:
# hibernate:
# format_sql: true
- 关键点: 将数据源配置放在
spring.datasource.primary
和spring.datasource.secondary
下,避免与 Spring Boot 默认的spring.datasource
冲突。主数据源的@Primary
注解将在配置类中指定。
步骤 3:创建数据库和表
创建数据库:
CREATE DATABASE IF NOT EXISTS db_primary; CREATE DATABASE IF NOT EXISTS db_secondary;
在
db_primary
中创建表:USE db_primary; CREATE TABLE IF NOT EXISTS users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL );
在
db_secondary
中创建表:USE db_secondary; CREATE TABLE IF NOT EXISTS logs ( id BIGINT AUTO_INCREMENT PRIMARY KEY, message TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
步骤 4:创建实体类 (如果使用 JPA)
User.java
(主数据源):package com.example.multidatasource.entity.primary; import javax.persistence.*; @Entity @Table(name = "users") @Data // Lombok public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; }
- 注意包路径:
com.example.multidatasource.entity.primary
- 注意包路径:
Log.java
(次数据源):package com.example.multidatasource.entity.secondary; import javax.persistence.*; @Entity @Table(name = "logs") @Data public class Log { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String message; @Column(name = "created_at") private java.sql.Timestamp createdAt; }
- 注意包路径:
com.example.multidatasource.entity.secondary
- 注意包路径:
步骤 5:配置主数据源 (Primary)
创建配置类 PrimaryDataSourceConfig.java
:
package com.example.multidatasource.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class PrimaryDataSourceConfig {
/**
* 创建主数据源 Bean
* @return 配置好的 DataSource
*/
@Primary // 标记为主数据源
@Bean(name = "primaryDataSource") // 指定 Bean 名称
@ConfigurationProperties("spring.datasource.primary") // 绑定配置前缀
public DataSource primaryDataSource() {
// Spring Boot 会自动根据类型选择合适的 DataSource (如 HikariDataSource)
return DataSourceBuilder.create().build();
}
/**
* 为主数据源创建 JdbcTemplate Bean
* @param dataSource 通过 @Qualifier 注入名为 "primaryDataSource" 的 Bean
* @return 配置好的 JdbcTemplate
*/
@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 如果使用 JPA, 需要在此配置 LocalContainerEntityManagerFactoryBean 和 PlatformTransactionManager
// 但为了清晰,通常将 JPA 配置放在单独的类中 (见后续步骤)
}
步骤 6:配置次数据源 (Secondary)
创建配置类 SecondaryDataSourceConfig.java
:
package com.example.multidatasource.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class SecondaryDataSourceConfig {
/**
* 创建次数据源 Bean
* @return 配置好的 DataSource
*/
@Bean(name = "secondaryDataSource") // 注意:没有 @Primary
@ConfigurationProperties("spring.datasource.secondary") // 绑定配置前缀
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 为次数据源创建 JdbcTemplate Bean
* @param dataSource 通过 @Qualifier 注入名为 "secondaryDataSource" 的 Bean
* @return 配置好的 JdbcTemplate
*/
@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
步骤 7:配置 JPA (如果使用 JPA)
如果使用 JPA,需要为每个数据源配置独立的 EntityManagerFactory
和 TransactionManager
。跳过此步骤如果只使用 JdbcTemplate。
主数据源 JPA 配置 (
PrimaryJpaConfig.java
):package com.example.multidatasource.config.jpa; import com.example.multidatasource.entity.primary.User; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration @EnableTransactionManagement // 启用注解式事务管理 @EnableJpaRepositories( basePackages = "com.example.multidatasource.repository.primary", // 指定该 EntityManagerFactory 管理的 Repository 包 entityManagerFactoryRef = "primaryEntityManagerFactory", // 指定 EntityManagerFactory Bean 名称 transactionManagerRef = "primaryTransactionManager" // 指定 TransactionManager Bean 名称 ) public class PrimaryJpaConfig { /** * 创建主数据源的 EntityManagerFactory Bean * @param builder EntityManagerFactoryBuilder * @param dataSource 通过 @Qualifier 注入主数据源 * @return LocalContainerEntityManagerFactoryBean */ @Primary @Bean(name = "primaryEntityManagerFactory") public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("primaryDataSource") DataSource dataSource) { Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.hbm2ddl.auto", "update"); // 根据需要调整 properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); properties.put("hibernate.show_sql", "true"); return builder .dataSource(dataSource) .packages(User.class) // 指定实体类包或具体类 .persistenceUnit("primary") // 持久化单元名称 .properties(properties) .build(); } /** * 创建主数据源的事务管理器 Bean * @param emf 通过 @Qualifier 注入主 EntityManagerFactory * @return PlatformTransactionManager */ @Primary @Bean(name = "primaryTransactionManager") public PlatformTransactionManager primaryTransactionManager( @Qualifier("primaryEntityManagerFactory") EntityManagerFactory emf) { return new JpaTransactionManager(emf); } }
次数据源 JPA 配置 (
SecondaryJpaConfig.java
):package com.example.multidatasource.config.jpa; import com.example.multidatasource.entity.secondary.Log; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration @EnableTransactionManagement @EnableJpaRepositories( basePackages = "com.example.multidatasource.repository.secondary", // 指定 Repository 包 entityManagerFactoryRef = "secondaryEntityManagerFactory", // 指定 EMF Bean 名称 transactionManagerRef = "secondaryTransactionManager" // 指定 TM Bean 名称 ) public class SecondaryJpaConfig { /** * 创建次数据源的 EntityManagerFactory Bean * @param builder * @param dataSource 通过 @Qualifier 注入次数据源 * @return */ @Bean(name = "secondaryEntityManagerFactory") public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("secondaryDataSource") DataSource dataSource) { Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.hbm2ddl.auto", "update"); properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); properties.put("hibernate.show_sql", "true"); return builder .dataSource(dataSource) .packages(Log.class) .persistenceUnit("secondary") .properties(properties) .build(); } /** * 创建次数据源的事务管理器 Bean * @param emf 通过 @Qualifier 注入次 EntityManagerFactory * @return */ @Bean(name = "secondaryTransactionManager") public PlatformTransactionManager secondaryTransactionManager( @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory emf) { return new JpaTransactionManager(emf); } }
步骤 8:创建 Repository (DAO)
方式 A:使用 JdbcTemplate
主数据源 Repository (
PrimaryUserRepository.java
):package com.example.multidatasource.repository; import com.example.multidatasource.entity.primary.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; @Repository public class PrimaryUserRepository { private final JdbcTemplate jdbcTemplate; // 构造函数注入,使用 @Qualifier 指定注入哪个 JdbcTemplate Bean @Autowired public PrimaryUserRepository(@Qualifier("primaryJdbcTemplate") JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public List<User> findAll() { String sql = "SELECT id, name, email FROM users"; return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); } public Optional<User> findById(Long id) { String sql = "SELECT id, name, email FROM users WHERE id = ?"; try { User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id); return Optional.ofNullable(user); } catch (Exception e) { return Optional.empty(); } } public void save(User user) { String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; jdbcTemplate.update(sql, user.getName(), user.getEmail()); } }
次数据源 Repository (
SecondaryLogRepository.java
):package com.example.multidatasource.repository; import com.example.multidatasource.entity.secondary.Log; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.List; @Repository public class SecondaryLogRepository { private final JdbcTemplate jdbcTemplate; @Autowired public SecondaryLogRepository(@Qualifier("secondaryJdbcTemplate") JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public List<Log> findAll() { String sql = "SELECT id, message, created_at FROM logs"; return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Log.class)); } public void save(Log log) { String sql = "INSERT INTO logs (message) VALUES (?)"; jdbcTemplate.update(sql, log.getMessage()); } }
方式 B:使用 JPA (如果配置了 JPA)
主数据源 Repository 接口 (
PrimaryUserRepository.java
):package com.example.multidatasource.repository.primary; import com.example.multidatasource.entity.primary.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface PrimaryUserRepository extends JpaRepository<User, Long> { // Spring Data JPA 自动生成实现 }
- 注意包路径:
com.example.multidatasource.repository.primary
- 注意包路径:
次数据源 Repository 接口 (
SecondaryLogRepository.java
):package com.example.multidatasource.repository.secondary; import com.example.multidatasource.entity.secondary.Log; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface SecondaryLogRepository extends JpaRepository<Log, Long> { }
- 注意包路径:
com.example.multidatasource.repository.secondary
- 注意包路径:
步骤 9:创建 Service 层
package com.example.multidatasource.service;
import com.example.multidatasource.entity.primary.User;
import com.example.multidatasource.entity.secondary.Log;
import com.example.multidatasource.repository.PrimaryUserRepository; // 使用 JdbcTemplate 版本
import com.example.multidatasource.repository.SecondaryLogRepository; // 使用 JdbcTemplate 版本
// import com.example.multidatasource.repository.primary.PrimaryUserRepository; // 使用 JPA 版本
// import com.example.multidatasource.repository.secondary.SecondaryLogRepository; // 使用 JPA 版本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class DataService {
private final PrimaryUserRepository primaryUserRepository;
private final SecondaryLogRepository secondaryLogRepository;
@Autowired
public DataService(PrimaryUserRepository primaryUserRepository,
SecondaryLogRepository secondaryLogRepository) {
this.primaryUserRepository = primaryUserRepository;
this.secondaryLogRepository = secondaryLogRepository;
}
// 读取主数据源
public List<User> getAllUsers() {
return primaryUserRepository.findAll();
}
// 写入主数据源
@Transactional(transactionManager = "primaryTransactionManager") // 如果使用 JPA, 指定 TM
public void createUser(User user) {
primaryUserRepository.save(user);
// 可以在这里记录操作日志到次数据源
logOperation("Created user: " + user.getName());
}
// 读取次数据源
public List<Log> getAllLogs() {
return secondaryLogRepository.findAll();
}
// 写入次数据源
@Transactional(transactionManager = "secondaryTransactionManager") // 如果使用 JPA, 指定 TM
private void logOperation(String message) {
Log log = new Log();
log.setMessage(message);
secondaryLogRepository.save(log);
}
}
步骤 10:创建 Controller 层
package com.example.multidatasource.controller;
import com.example.multidatasource.entity.primary.User;
import com.example.multidatasource.entity.secondary.Log;
import com.example.multidatasource.service.DataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
public class DataController {
private final DataService dataService;
@Autowired
public DataController(DataService dataService) {
this.dataService = dataService;
}
@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(dataService.getAllUsers());
}
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
dataService.createUser(user);
return ResponseEntity.ok(user);
}
@GetMapping("/logs")
public ResponseEntity<List<Log>> getAllLogs() {
return ResponseEntity.ok(dataService.getAllLogs());
}
}
步骤 11:启动应用并测试
- 确保两个数据库 (
db_primary
,db_secondary
) 和对应的表 (users
,logs
) 已创建。 - 运行 Spring Boot 应用。
- 测试 API:
GET http://localhost:8080/api/users
-> 应返回db_primary.users
表的数据。POST http://localhost:8080/api/users
(JSON Body) -> 应在db_primary.users
插入数据,并在db_secondary.logs
记录日志。GET http://localhost:8080/api/logs
-> 应返回db_secondary.logs
表的数据。
三、常见错误
No qualifying bean of type 'javax.sql.DataSource' available
或NoUniqueBeanDefinitionException
:- 原因: Spring Boot 试图自动配置
DataSource
,但在多数据源场景下,它不知道该配置哪个,或者没有明确的@Primary
数据源。 - 解决: 确保手动配置所有
DataSource
Bean,并且为其中一个DataSource
Bean 添加@Primary
注解。
- 原因: Spring Boot 试图自动配置
UnsatisfiedDependencyException
注入JdbcTemplate
/EntityManagerFactory
/TransactionManager
失败:- 原因: 存在多个同类型的 Bean,Spring 不知道注入哪一个。
- 解决: 使用
@Qualifier("beanName")
注解明确指定要注入的 Bean 名称。检查@Bean
方法的名称或@Component
类的名称。
InvalidDataAccessApiUsageException
/PersistenceException
:No EntityManager with actual transaction available for current thread
:- 原因 (JPA): 在需要事务的方法上缺少
@Transactional
注解,或者@Transactional
注解没有正确指定transactionManager
。 - 解决 (JPA): 对于写操作(增删改),确保 Service 方法上有
@Transactional
。如果使用多事务管理器,使用@Transactional(transactionManager = "yourTransactionManagerBeanName")
指定。
- 原因 (JPA): 在需要事务的方法上缺少
BeanCreationException
创建EntityManagerFactory
失败:- 原因:
@EnableJpaRepositories
的basePackages
指向了错误的包,或者实体类 (@Entity
) 没有被扫描到。 - 解决: 检查
@EnableJpaRepositories(basePackages = ...)
的包路径是否正确包含了对应的 Repository 接口和实体类。确保实体类在正确的包下。
- 原因:
SQLSyntaxErrorException
/TableNotFoundException
:- 原因: SQL 语句中的表名或列名错误,或者数据库连接指向了错误的数据库。
- 解决: 仔细检查 SQL 语句;确认
DataSource
的 URL 配置正确,连接到了预期的数据库。
DataSource
配置未生效 (@ConfigurationProperties
不工作):- 原因:
@ConfigurationProperties
的前缀与application.yml
中的配置路径不匹配。 - 解决: 仔细核对
@ConfigurationProperties("prefix")
中的prefix
是否与application.yml
中的配置节点完全一致(包括拼写和层级)。
- 原因:
JdbcTemplate
Bean 注入了错误的数据源:- 原因: 创建
JdbcTemplate
Bean 时,注入的DataSource
参数没有使用@Qualifier
,导致注入了@Primary
的数据源。 - 解决: 在创建非主
JdbcTemplate
的@Bean
方法中,为其DataSource
参数添加@Qualifier("dataSourceBeanName")
。
- 原因: 创建
四、注意事项
- 禁用自动配置: 在多数据源配置中,不要依赖 Spring Boot 对
DataSource
的自动配置。必须手动创建所有DataSource
Bean。 @Primary
的必要性: 必须且只能有一个DataSource
Bean 被标记为@Primary
,以解决自动装配的歧义。@Qualifier
的使用: 当需要注入非主 Bean 时,@Qualifier
是必不可少的。- Bean 名称:
@Bean
方法的名称默认就是 Bean 的名称。确保@Qualifier
中引用的名称与@Bean
方法名一致。 - JPA 配置复杂性: 使用 JPA 时,配置
EntityManagerFactory
和TransactionManager
会显著增加复杂度。确保@EnableJpaRepositories
的配置正确。 - 事务边界: 在多数据源环境下,默认的
@Transactional
只能管理一个数据源的事务。跨数据源的分布式事务需要使用 JTA (如 Atomikos, Bitronix) 或最终一致性方案(如 Saga 模式),这非常复杂且性能开销大,应尽量避免。 - 连接池监控: 为每个数据源配置独立的连接池监控(如 HikariCP 的 metrics),以便观察各自的数据源健康状况。
- 配置隔离: 将不同数据源的配置分散到不同的配置类中,保持代码清晰。
- 测试: 多数据源应用的集成测试需要更谨慎,确保测试数据在正确的数据库中。
五、使用技巧
使用常量类定义 Bean 名称: 避免在代码中硬编码 Bean 名称,减少拼写错误。
public class DataSourceConstants { public static final String PRIMARY_DATASOURCE = "primaryDataSource"; public static final String SECONDARY_DATASOURCE = "secondaryDataSource"; public static final String PRIMARY_JDBC_TEMPLATE = "primaryJdbcTemplate"; // ... } // 使用: @Qualifier(DataSourceConstants.PRIMARY_DATASOURCE)
抽象配置父类 (可选): 如果多个数据源配置相似,可以创建一个父类或使用
@ConfigurationProperties
的嵌套对象来减少重复代码。动态数据源 (
AbstractRoutingDataSource
): 实现更复杂的路由逻辑,如读写分离。- 创建一个
RoutingDataSource
继承AbstractRoutingDataSource
。 - 重写
determineCurrentLookupKey()
方法,根据 ThreadLocal 变量(如DataSourceContextHolder
)决定使用哪个数据源。 - 在 AOP 切面或方法调用前设置数据源键。
- 注意: 这增加了复杂性,需谨慎使用。
- 创建一个
使用
@ConfigurationProperties
: 这是绑定外部配置到 Java 对象的标准方式,比手动@Value
注解更清晰、更安全。为
DataSource
指定唯一名称: 在HikariDataSource
中设置poolName
,便于在日志和监控中区分。JdbcTemplate
命名: 为每个JdbcTemplate
Bean 起一个有意义的名称(如primaryJdbcTemplate
),方便识别。
六、最佳实践
- 明确主次: 清晰定义哪个是主数据源(
@Primary
),哪个是次数据源。 - 配置类分离: 将每个数据源的
DataSource
,JdbcTemplate
, (JPA 的)EntityManagerFactory
,TransactionManager
配置放在独立的@Configuration
类中。 - 包结构清晰: 为不同数据源的实体类、Repository、Service 建立独立的包结构(如
entity.primary
,repository.secondary
)。 - Repository 层隔离: 确保 Repository 只访问其对应的数据源。
- Service 层协调: Service 层负责协调多个 Repository 的调用。
- 避免跨数据源事务: 除非绝对必要,否则避免在单个事务中操作多个数据源。采用补偿事务、消息队列等最终一致性方案。
- 使用连接池: 务必使用 HikariCP 等高性能连接池。
- 监控: 集成 Micrometer 等监控框架,暴露各数据源连接池的指标。
- 文档化: 记录数据源的用途、配置和访问方式。
- 安全性: 确保数据库凭证安全存储(如使用 Spring Cloud Config Server, HashiCorp Vault, 或环境变量)。
七、性能优化
连接池优化:
- HikariCP: 精细调整
maximum-pool-size
,minimum-idle
,connection-timeout
,idle-timeout
,max-lifetime
。启用预处理语句缓存 (cachePrepStmts
,prepStmtCacheSize
,prepStmtCacheSqlLimit
,useServerPrepStmts
)。 - 监控: 实时监控连接池的活跃连接、空闲连接、等待线程数,及时发现瓶颈。
- HikariCP: 精细调整
SQL 优化:
- 索引: 为频繁查询的字段建立索引。
- 查询优化: 避免
SELECT *
,使用分页,优化JOIN
和WHERE
条件。 - 批量操作: 对大量数据使用
JdbcTemplate.batchUpdate
。
缓存:
- 应用层缓存: 使用
@Cacheable
缓存频繁读取且不常变的数据(如配置信息、字典表),减少数据库访问。 - 数据库缓存: 利用数据库自身的查询缓存(如果适用)。
- 应用层缓存: 使用
读写分离:
- 将读操作路由到从库,写操作路由到主库,分散主库压力。
- 可通过
AbstractRoutingDataSource
+ AOP 实现,或使用 ShardingSphere 等专业中间件。
异步处理:
- 对于非关键路径的数据库操作(如记录日志、发送通知),使用
@Async
在后台线程执行,提高主流程响应速度。
- 对于非关键路径的数据库操作(如记录日志、发送通知),使用
减少网络开销:
- 确保应用服务器与数据库服务器之间的网络延迟尽可能低。
- 使用连接池减少连接建立的开销。
监控与分析:
- 使用 APM 工具(如 SkyWalking, Zipkin)追踪 SQL 执行时间。
- 开启数据库慢查询日志,分析并优化慢 SQL。
- 监控各数据源的 QPS、响应时间、错误率。
总结: Spring Boot 多数据源配置需要手动管理 DataSource
、JdbcTemplate
/EntityManagerFactory
和 TransactionManager
Bean。核心是使用 @Primary
指定主数据源,并用 @Qualifier
精确注入非主 Bean。配置应清晰分离,遵循分层架构。务必注意事务管理的局限性(单数据源事务)。通过合理的连接池配置、SQL 优化、缓存和读写分离等手段,可以有效提升多数据源应用的性能。