SpringBoot动态数据源多数据源自动切换
在现代企业级应用中,为了提高系统的可维护性和扩展性,多数据源的配置和使用变得越来越常见。特别是在处理跨数据库操作、读写分离、分库分表等场景时,动态数据源切换技术显得尤为重要。本文将介绍如何在Spring Boot项目中实现多数据源的自动切换。
1. 引言
随着业务的不断增长,单一的数据源往往难以满足高并发、大数据量的需求。通过引入多数据源,可以有效地解决这些问题。Spring Boot提供了灵活的配置机制,使得多数据源的管理变得更加简便。
2. 环境准备
- Java版本:1.8+
- Spring Boot版本:2.3.4.RELEASE
- 数据库:MySQL 5.7+
3. 添加依赖
首先,在pom.xml文件中添加必要的依赖:
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- HikariCP 数据源 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>4. 配置多数据源
在application.yml中配置多个数据源:
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/db_master?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/db_slave?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver5. 创建数据源配置类
创建一个配置类来管理多个数据源,并将其注册到Spring容器中:
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.datasource.lookup.AbstractRoutingDataSource;
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public HikariDataSource masterDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public HikariDataSource slaveDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "dynamicDataSource")
public AbstractRoutingDataSource dynamicDataSource(@Qualifier("masterDataSource") HikariDataSource masterDataSource,
@Qualifier("slaveDataSource") HikariDataSource slaveDataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("master", masterDataSource);
dataSourceMap.put("slave", slaveDataSource);
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
}6. 实现动态数据源切换
创建一个DynamicDataSource类继承AbstractRoutingDataSource,并重写determineCurrentLookupKey方法来决定当前使用的数据源:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}创建一个DataSourceContextHolder类来管理数据源上下文:
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();
}
}7. 使用AOP实现自动切换
使用Spring AOP来实现数据源的自动切换:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.demo.annotation.TargetDataSource)")
public void dataSourcePointCut() {}
@Before("dataSourcePointCut()")
public void switchDataSource(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
TargetDataSource targetDataSource = signature.getMethod().getAnnotation(TargetDataSource.class);
if (targetDataSource != null) {
DataSourceContextHolder.setDataSource(targetDataSource.value());
}
}
}创建一个自定义注解TargetDataSource用于标记需要切换数据源的方法:
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}8. 测试
创建一些测试用的服务类和控制器来验证多数据源切换是否正常工作:
import com.example.demo.annotation.TargetDataSource;
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;
@TargetDataSource("master")
public List<Map<String, Object>> getUserFromMaster() {
return jdbcTemplate.queryForList("SELECT * FROM user");
}
@TargetDataSource("slave")
public List<Map<String, Object>> getUserFromSlave() {
return jdbcTemplate.queryForList("SELECT * FROM user");
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/master")
public List<Map<String, Object>> getUsersFromMaster() {
return userService.getUserFromMaster();
}
@GetMapping("/slave")
public List<Map<String, Object>> getUsersFromSlave() {
return userService.getUserFromSlave();
}
}在实际应用中,特别是在大型系统或微服务架构中,经常会遇到需要操作多个数据库的情况。Spring Boot 提供了灵活的配置和扩展机制,可以方便地实现多数据源的支持。下面是一个简单的示例,展示如何在 Spring Boot 应用中配置和使用动态数据源。
1. 添加依赖
首先,在 pom.xml 文件中添加必要的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>2. 配置多数据源
在 application.yml 中配置两个数据源:
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/db2?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver3. 创建数据源配置类
创建一个配置类来配置多个数据源,并将它们注册到 Spring 容器中:
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.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 DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@Primary
public DynamicDataSource dataSource(@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;
}
}4. 创建动态数据源类
创建一个继承自 AbstractRoutingDataSource 的类来实现数据源的动态切换:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}5. 创建数据源上下文管理类
创建一个工具类来管理数据源的切换:
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();
}
}6. 使用 AOP 切换数据源
使用 AOP 来切面管理数据源的切换,例如根据方法注解来切换数据源:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(com.example.demo.annotation.TargetDataSource)")
public void changeDataSource(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
TargetDataSource targetDataSource = signature.getMethod().getAnnotation(TargetDataSource.class);
if (targetDataSource != null) {
DataSourceContextHolder.setDataSource(targetDataSource.value());
}
}
@After("@annotation(com.example.demo.annotation.TargetDataSource)")
public void restoreDataSource(JoinPoint point) {
DataSourceContextHolder.clearDataSource();
}
}7. 自定义注解
创建一个自定义注解来标记需要切换数据源的方法:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
String value();
}8. 使用示例
在服务层中使用自定义注解来切换数据源:
import com.example.demo.annotation.TargetDataSource;
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;
@TargetDataSource("primary")
public void doSomethingWithPrimaryDB() {
jdbcTemplate.execute("SELECT * FROM user");
}
@TargetDataSource("secondary")
public void doSomethingWithSecondaryDB() {
jdbcTemplate.execute("SELECT * FROM user");
}
}在Spring Boot应用中实现多数据源的动态切换是一个常见的需求,尤其是在需要访问多个数据库或不同环境下的数据库时。下面将详细介绍如何在Spring Boot中配置和使用动态数据源。
1. 添加依赖
首先,确保你的pom.xml文件中包含必要的依赖项。通常情况下,你需要Spring Boot的Web和JPA/MyBatis等ORM框架的依赖,以及相应的数据库驱动依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- 其他数据库驱动依赖 -->
</dependencies>2. 配置数据源
在application.yml或application.properties文件中配置多个数据源。
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/db2?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver3. 创建数据源配置类
创建一个配置类来定义和配置多个数据源。
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return new BasicDataSource();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return new BasicDataSource();
}
@Bean
public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("primary", primaryDataSource);
targetDataSources.put("secondary", secondaryDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
return dynamicDataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}4. 创建动态数据源类
创建一个自定义的DynamicDataSource类,用于管理多个数据源的切换。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}5. 创建数据源上下文持有类
创建一个线程安全的上下文持有类,用于存储当前线程的数据源标识。
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();
}
}6. 创建数据源切换注解
创建一个注解,用于在方法或类上标记数据源切换。
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "";
}7. 创建切面类
创建一个切面类,用于拦截带有@DataSource注解的方法或类,并进行数据源切换。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.demo.DataSource)")
public void dataSourcePointCut() {}
@Before("dataSourcePointCut() && @annotation(dataSource)")
public void switchDataSource(DataSource dataSource) {
DataSourceContextHolder.setDataSource(dataSource.value());
}
}8. 使用示例
在Service层或Controller层使用@DataSource注解来切换数据源。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@DataSource("primary")
public User getUserFromPrimary(int id) {
return userRepository.findById(id).orElse(null);
}
@DataSource("secondary")
public User getUserFromSecondary(int id) {
return userRepository.findById(id).orElse(null);
}
}9. 配置JPA或MyBatis
如果你使用的是JPA或MyBatis,需要确保它们能够正确地使用动态数据源。对于JPA,你可能需要配置实体管理器工厂;对于MyBatis,需要配置SqlSessionFactory。
JPA配置
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
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;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.demo.repository",
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager"
)
public class JpaConfig {
@Autowired
private JpaProperties jpaProperties;
@Autowired
@Qualifier("dynamicDataSource")
private DataSource dynamicDataSource;
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dynamicDataSource)
.properties(jpaProperties.getProperties())
.packages("com.example.demo.entity")
.persistenceUnit("default")
.build();
}
@Primary
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}MyBatis配置
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.example.demo.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisConfig {
@Autowired
@Qualifier("dynamicDataSource")
private DataSource dynamicDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dynamicDataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}通过以上步骤,你可以在Spring Boot应用中实现多数据源的动态切换。希望这些信息对你有所帮助!如果有任何问题或需要进一步的帮助,请随时提问。










