博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
DeepSeek-行业融合之万象视界(附实战案例详解100+)
全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
感兴趣的可以先收藏起来,希望帮助更多的人
在现代企业级应用开发中,一个应用系统可能需要同时访问多个数据源,例如主从数据库架构、多租户系统等。Spring Boot 作为一个快速开发框架,提供了便捷的方式来处理多数据源问题。其中,AbstractRoutingDataSource
是 Spring 框架中用于动态切换数据源的核心类。本文将详细介绍如何使用 AbstractRoutingDataSource
实现 Spring Boot 多数据源动态切换方案。
在高并发的应用场景中,为了提高数据库的读写性能,通常会采用主从数据库架构。主数据库负责写操作,从数据库负责读操作。应用程序需要根据不同的业务需求动态切换到主库或从库进行数据操作。
多租户系统是指多个租户共享同一个应用程序实例,但每个租户有自己独立的数据库。应用程序需要根据不同的租户信息动态切换到相应的数据库进行数据操作。
AbstractRoutingDataSource
是 Spring 框架中的一个抽象类,它实现了 javax.sql.DataSource
接口,用于动态切换数据源。该类通过重写 determineCurrentLookupKey()
方法来确定当前使用的数据源。
AbstractRoutingDataSource
的 targetDataSources
属性中。AbstractRoutingDataSource
的 defaultTargetDataSource
属性设置默认数据源。determineCurrentLookupKey()
方法返回当前使用的数据源的键,AbstractRoutingDataSource
根据该键从 targetDataSources
中获取相应的数据源。首先,创建一个 Spring Boot 项目,添加以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
dependencies>
在 application.properties
中配置多个数据源:
# 主数据源
spring.datasource.master.url=jdbc:mysql://localhost:3306/master_db
spring.datasource.master.username=root
spring.datasource.master.password=root
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
# 从数据源
spring.datasource.slave.url=jdbc:mysql://localhost:3306/slave_db
spring.datasource.slave.username=root
spring.datasource.slave.password=root
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
创建一个枚举类来定义数据源的键:
public enum DataSourceType {
MASTER, SLAVE
}
创建一个数据源上下文持有者类,用于在 ThreadLocal 中存储当前使用的数据源的键:
public class DataSourceContextHolder {
private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();
public static void setDataSource(DataSourceType dataSourceType) {
contextHolder.set(dataSourceType);
}
public static DataSourceType getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
创建一个继承自 AbstractRoutingDataSource
的动态数据源类,重写 determineCurrentLookupKey()
方法:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
创建一个配置类,配置多个数据源和动态数据源:
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.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource);
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
创建一个自定义注解,用于在业务方法上指定使用的数据源:
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSwitch {
DataSourceType value() default DataSourceType.MASTER;
}
创建一个切面类,在业务方法执行前根据注解设置当前使用的数据源:
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.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class DataSourceSwitchAspect {
@Before("@annotation(com.example.demo.DataSourceSwitch)")
public void before(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSourceSwitch dataSourceSwitch = method.getAnnotation(DataSourceSwitch.class);
if (dataSourceSwitch != null) {
DataSourceContextHolder.setDataSource(dataSourceSwitch.value());
}
}
@After("@annotation(com.example.demo.DataSourceSwitch)")
public void after(JoinPoint point) {
DataSourceContextHolder.clearDataSource();
}
}
在业务方法上使用 DataSourceSwitch
注解指定使用的数据源:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@DataSourceSwitch(DataSourceType.MASTER)
public void insertUser(String name) {
String sql = "INSERT INTO user (name) VALUES (?)";
jdbcTemplate.update(sql, name);
}
@DataSourceSwitch(DataSourceType.SLAVE)
public String getUserName(int id) {
String sql = "SELECT name FROM user WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{id}, String.class);
}
}
由于 DataSourceContextHolder
使用了 ThreadLocal
来存储当前使用的数据源的键,因此在多线程环境下需要注意线程安全问题。确保在每个线程中正确设置和清除数据源信息。
在使用多数据源时,需要注意事务管理的问题。如果一个业务方法涉及到多个数据源的操作,需要使用分布式事务管理方案,如 Seata 等。
通过使用 AbstractRoutingDataSource
,我们可以很方便地实现 Spring Boot 多数据源动态切换方案。本文详细介绍了 AbstractRoutingDataSource
的原理和使用方法,并给出了具体的代码示例。在实际应用中,我们可以根据业务需求灵活配置和使用多数据源,提高应用程序的性能和可扩展性。