Spring Boot 多数据源配置与动态切换策略:一场数据世界的华丽冒险
各位看官,咱们今天聊点硬核的,关于Spring Boot多数据源配置和动态切换的那些事儿。想象一下,你是一个管理着庞大帝国的君王,手握着各种各样的数据资源,分布在不同的城堡(数据库)里。有的城堡存储着用户的喜好,有的城堡记录着商品的库存,还有的城堡负责处理订单的流转。你如何才能有效地管理这些城堡,并且在需要的时候快速地访问它们呢?这就是多数据源配置和动态切换要解决的问题。
Spring Boot,作为你的忠实管家,提供了强大的工具,帮助你轻松驾驭这场数据世界的华丽冒险。让我们一起深入探索,揭开它的神秘面纱。
为什么要玩多数据源?
首先,咱们得搞清楚,为什么我们需要多个数据库?难道一个数据库不够用吗?当然不是,理由多种多样,就像你总需要好几双鞋来搭配不同的场合一样:
-
业务隔离: 不同的业务模块可能需要使用不同的数据库,以实现数据隔离,提高系统的稳定性和安全性。例如,用户数据和交易数据分开存储,避免相互干扰。
-
性能优化: 某些数据库可能更适合特定类型的操作。例如,使用MySQL存储结构化数据,使用MongoDB存储非结构化数据,充分发挥各自的优势。
-
分库分表: 当单个数据库的数据量过大时,可以采用分库分表策略,将数据分散到多个数据库中,提高查询效率。
-
历史遗留问题: 有些项目可能已经使用了多个数据库,为了保持兼容性,需要继续使用多数据源。
总之,多数据源可以帮助我们更好地组织和管理数据,提高系统的性能、稳定性和可扩展性。
多数据源配置:搭建城堡的基础
在Spring Boot中配置多数据源,就像为你的王国建造不同的城堡一样,需要精心设计和规划。
1. 引入依赖:
首先,我们需要引入相关的依赖,例如MySQL驱动:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
当然,如果你要使用其他的数据库,比如PostgreSQL,那就引入对应的依赖。
2. 配置数据源:
接下来,我们需要在application.properties
或application.yml
文件中配置数据源的信息。假设我们有两个数据库,一个是primary
,一个是secondary
:
# Primary Database
spring.datasource.primary.url=jdbc:mysql://localhost:3306/primary_db?serverTimezone=UTC&useSSL=false
spring.datasource.primary.username=root
spring.datasource.primary.password=password
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
# Secondary Database
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondary_db?serverTimezone=UTC&useSSL=false
spring.datasource.secondary.username=root
spring.datasource.secondary.password=password
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
3. 创建数据源配置类:
现在,我们需要创建一个配置类,来告诉Spring Boot如何创建和管理这些数据源。
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource primaryDataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource secondaryDataSource(@Qualifier("secondaryDataSourceProperties") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
这段代码做了什么呢?
@Configuration
: 告诉Spring这是一个配置类。@ConfigurationProperties("spring.datasource.primary")
: 将application.properties
中以spring.datasource.primary
开头的配置项绑定到DataSourceProperties
对象上。@Primary
: 标记primaryDataSource
为默认的数据源。如果没有指定具体的数据源,Spring Boot会默认使用这个数据源。@Qualifier
: 用于消除歧义,当有多个相同类型的Bean时,可以使用@Qualifier
来指定要注入的Bean。DataSourceProperties
是 Spring Boot 提供的一个类,用于封装数据源的配置信息。HikariDataSource
是一个高性能的 JDBC 连接池,Spring Boot 默认使用它作为数据源。JdbcTemplate
是 Spring JDBC 提供的一个工具类,用于简化 JDBC 操作。
4. 使用数据源:
现在,我们就可以在代码中使用这两个数据源了。例如,我们可以创建一个Service类,分别使用primaryJdbcTemplate
和secondaryJdbcTemplate
来操作不同的数据库:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Autowired
@Qualifier("primaryJdbcTemplate")
private JdbcTemplate primaryJdbcTemplate;
@Autowired
@Qualifier("secondaryJdbcTemplate")
private JdbcTemplate secondaryJdbcTemplate;
public String getDataFromPrimary() {
return primaryJdbcTemplate.queryForObject("SELECT data FROM primary_table WHERE id = 1", String.class);
}
public String getDataFromSecondary() {
return secondaryJdbcTemplate.queryForObject("SELECT data FROM secondary_table WHERE id = 1", String.class);
}
}
在这个例子中,我们使用@Qualifier
注解来指定要注入的JdbcTemplate
对象。getDataFromPrimary()
方法使用primaryJdbcTemplate
来查询primary_table
表的数据,getDataFromSecondary()
方法使用secondaryJdbcTemplate
来查询secondary_table
表的数据。
动态数据源切换:城堡的灵活调度
仅仅配置好多个数据源是不够的,我们还需要能够根据不同的业务场景,动态地切换数据源。就像君王需要根据不同的战况,调动不同的军队一样,我们需要能够根据不同的业务需求,选择不同的数据库。
1. 定义数据源上下文:
首先,我们需要定义一个数据源上下文,用于存储当前线程使用的数据源。
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
这个类使用了ThreadLocal
来存储数据源的名称,保证每个线程都有自己的数据源。
2. 创建动态数据源:
接下来,我们需要创建一个动态数据源,用于根据上下文中的数据源名称,选择实际的数据源。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
AbstractRoutingDataSource
是Spring提供的一个抽象类,用于实现动态数据源。determineCurrentLookupKey()
方法用于确定当前的数据源的Key,这里我们从DataSourceContextHolder
中获取数据源的名称。
3. 配置动态数据源:
现在,我们需要在配置类中配置动态数据源。
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource primaryDataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
public DataSource secondaryDataSource(@Qualifier("secondaryDataSourceProperties") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public AbstractRoutingDataSource routingDataSource(
@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("primary", primaryDataSource);
targetDataSources.put("secondary", secondaryDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
return dynamicDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(@Qualifier("routingDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
在这个配置类中,我们创建了一个routingDataSource
Bean,它是一个DynamicDataSource
对象。我们将primaryDataSource
和secondaryDataSource
添加到targetDataSources
中,并设置primaryDataSource
为默认的数据源。
4. 使用动态数据源:
现在,我们就可以在代码中使用动态数据源了。例如,我们可以创建一个Service类,根据不同的业务逻辑,切换不同的数据源:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Autowired
private JdbcTemplate jdbcTemplate;
public String getDataFromPrimary() {
DataSourceContextHolder.setDataSource("primary");
try {
return jdbcTemplate.queryForObject("SELECT data FROM primary_table WHERE id = 1", String.class);
} finally {
DataSourceContextHolder.clearDataSource();
}
}
public String getDataFromSecondary() {
DataSourceContextHolder.setDataSource("secondary");
try {
return jdbcTemplate.queryForObject("SELECT data FROM secondary_table WHERE id = 1", String.class);
} finally {
DataSourceContextHolder.clearDataSource();
}
}
}
在这个例子中,getDataFromPrimary()
方法在执行查询之前,将数据源设置为primary
,执行查询之后,清除数据源。getDataFromSecondary()
方法类似,只是将数据源设置为secondary
。
5. AOP 拦截实现动态切换 (更优雅的方式):
上面的手动设置方式,在每个方法里都需要添加 DataSourceContextHolder.setDataSource()
和 DataSourceContextHolder.clearDataSource()
,显得比较冗余。我们可以使用 AOP 来拦截方法,自动切换数据源,让代码更简洁优雅。
首先,我们需要定义一个注解,用于标记需要切换数据源的方法:
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String dataSource() default "primary"; // 默认使用 primary 数据源
}
然后,创建一个 AOP 切面,拦截带有 @TargetDataSource
注解的方法,并在方法执行前后切换数据源:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(TargetDataSource)")
public void dataSourcePointCut() {
}
@Before("dataSourcePointCut()")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
if (targetDataSource != null) {
DataSourceContextHolder.setDataSource(targetDataSource.dataSource());
System.out.println("切换数据源为: " + targetDataSource.dataSource()); // 可以加日志方便调试
}
}
@After("dataSourcePointCut()")
public void after(JoinPoint joinPoint) {
DataSourceContextHolder.clearDataSource();
System.out.println("清除数据源"); // 可以加日志方便调试
}
}
最后,在你的 Service 类中使用 @TargetDataSource
注解:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Autowired
private JdbcTemplate jdbcTemplate;
@TargetDataSource(dataSource = "primary")
public String getDataFromPrimary() {
return jdbcTemplate.queryForObject("SELECT data FROM primary_table WHERE id = 1", String.class);
}
@TargetDataSource(dataSource = "secondary")
public String getDataFromSecondary() {
return jdbcTemplate.queryForObject("SELECT data FROM secondary_table WHERE id = 1", String.class);
}
}
现在,你只需要在方法上添加 @TargetDataSource
注解,就可以自动切换数据源了,是不是优雅多了?
总结:
总的来说,Spring Boot多数据源配置和动态切换是一个强大的工具,可以帮助我们更好地管理和使用数据。但是,它也带来了一些复杂性。我们需要 carefully design 我们的数据源策略,并确保数据源之间的切换是线程安全的。希望这篇文章能够帮助你理解Spring Boot多数据源配置和动态切换的原理和使用方法,让你在数据世界的冒险中更加游刃有余。
注意事项:
- 事务管理: 在使用多数据源时,需要特别注意事务管理。如果需要在多个数据源上执行事务操作,可以使用分布式事务管理器,例如Atomikos或Bitronix。
- 连接池: 选择合适的连接池,并根据实际情况调整连接池的参数,以提高系统的性能。
- 异常处理: 在切换数据源时,需要注意异常处理,避免出现数据不一致的情况。
希望这篇文章能让你对 Spring Boot 的多数据源配置和动态切换有一个更深入的了解。记住,实践才是检验真理的唯一标准,动手尝试一下,你就能真正掌握它了!Happy coding!