若依框架(RuoYi)是一款基于Spring Boot和Spring Cloud的开源快速开发平台,它提供了丰富的功能模块和灵活的扩展性。在实际项目中,动态数据源切换是一个常见的需求,例如多租户系统、分库分表场景等。本文将详细介绍如何在若依框架中实现动态数据源切换,包括技术原理、实现步骤以及代码示例。
动态数据源的核心思想是根据业务逻辑或上下文环境,在运行时切换到不同的数据库连接。其实现主要依赖以下几个关键点:
AbstractRoutingDataSource
类(Spring提供的一个抽象类),定义数据源路由规则。ThreadLocal
存储当前线程的数据源标识,确保每个线程能够独立切换数据源。确保项目中已引入Spring Boot相关的依赖。如果需要操作数据库,还需引入对应的数据库驱动。以MySQL为例,pom.xml中的依赖如下:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在application.yml
中配置多个数据源信息。例如:
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
继承AbstractRoutingDataSource
并重写determineCurrentLookupKey
方法,用于返回当前线程的数据源标识。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
使用ThreadLocal
存储当前线程的数据源标识。
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();
}
}
编写配置类,将多个数据源注入到动态数据源中。
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 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();
}
@Primary
@Bean(name = "dynamicDataSource")
public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
return dynamicDataSource;
}
}
在业务代码中调用DataSourceContextHolder
切换数据源。例如:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void switchToMasterAndQuery() {
DataSourceContextHolder.setDataSource("master");
List<User> users = userRepository.findAll();
DataSourceContextHolder.clearDataSource(); // 清除数据源上下文
}
public void switchToSlaveAndQuery() {
DataSourceContextHolder.setDataSource("slave");
List<User> users = userRepository.findAll();
DataSourceContextHolder.clearDataSource();
}
}
以下是动态数据源切换的流程图,展示了从配置到切换的完整过程:
graph TD A[初始化多个数据源] --> B[注册到DynamicDataSource] B --> C[设置默认数据源] D[业务代码调用] --> E[切换数据源(使用ThreadLocal)] E --> F[执行数据库操作] F --> G[恢复默认数据源或清除上下文]
ThreadLocal
保证了线程级别的隔离,但在异步任务或线程池场景下需要注意清理上下文,避免数据源泄漏。