SpringBoot 项目配置动态数据源
目录
- 一、前言
- 二、操作
- 1、引入依赖
- 2、配置默认数据库 1
- 3、定义数据源实体和 Repository
- 4、定义动态数据源
- 5、配置数据源
- 6、定义切换数据源注解
- 7、定义切面类
- 8、使用注解切换数据源
一、前言
通过切面注解方式根据不同业务动态切换数据库
二、操作
1、引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
2、配置默认数据库 1
- 在 application.properties 或 application.yml 配置数据库 1 信息:
spring.datasource.url=jdbc:mysql://localhost:3306/db1
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3、定义数据源实体和 Repository
- 数据源实体 DataSourceConfig.java:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class DataSourceEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String url;
private String username;
private String password;
private String driverClassName;
private String dataSourceKey; // 新增字段
// Getters 和 Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getDataSourceKey() {
return dataSourceKey;
}
public void setDataSourceKey(String dataSourceKey) {
this.dataSourceKey = dataSourceKey;
}
}
- Repository 接口 DataSourceConfigRepository.java:
import org.springframework.data.jpa.repository.JpaRepository;
public interface DataSourceConfigRepository extends JpaRepository<DataSourceEntity, Long> {
}
4、定义动态数据源
- 动态数据源上下文持有者 DynamicDataSourceContextHolder.java:
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
- 动态数据源类 DynamicDataSource.java:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
5、配置数据源
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
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.List;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Autowired
private DataSourceConfigRepository dataSourceConfigRepository;
@Primary
@Bean
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
// 从数据库 1 的 datasource 表加载所有数据源
List<DataSourceEntity> dataSourceConfigs = dataSourceConfigRepository.findAll();
for (DataSourceEntity config : dataSourceConfigs) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(config.getUrl());
dataSource.setUsername(config.getUsername());
dataSource.setPassword(config.getPassword());
dataSource.setDriverClassName(config.getDriverClassName());
targetDataSources.put(config.getId().toString(), dataSource);
if ("db1".equals(config.getDataSourceKey())) { // 假设表中有一个字段表示数据源的 key
dynamicDataSource.setDefaultTargetDataSource(dataSource);
}
}
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
6、定义切换数据源注解
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSwitch {
String value() default "db1";
}
7、定义切面类
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;
import java.util.List;
@Aspect
@Component
public class DataSourceSwitchAspect {
@Autowired
private DataSourceConfigRepository dataSourceConfigRepository;
@Before("@annotation(com.example.annotation.DataSourceSwitch)")
public void before(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSourceSwitch dataSourceSwitch = method.getAnnotation(DataSourceSwitch.class);
if (dataSourceSwitch != null) {
String dataSourceKey = dataSourceSwitch.value();
List<DataSourceEntity> dataSourceConfigs = dataSourceConfigRepository.findAll();
for (DataSourceConfig config : dataSourceConfigs) {
if (dataSourceKey.equals(config.getDataSourceKey())) {
DynamicDataSourceContextHolder.setDataSourceKey(config.getId().toString());
break;
}
}
}
}
@After("@annotation(com.example.annotation.DataSourceSwitch)")
public void after(JoinPoint point) {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
8、使用注解切换数据源
import com.example.annotation.DataSourceSwitch;
import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@DataSourceSwitch("db1")
public List<User> getUsersFromDb1() {
return userRepository.findAll();
}
@DataSourceSwitch("db2") // 假设 db2 是从 datasource 表获取的数据源 key
public List<User> getUsersFromDb2() {
return userRepository.findAll();
}
}