SpringBoot中配置多个数据源

说明

在Spring Boot中配置多个数据源并实现自动切换,可以通过使用AbstractRoutingDataSource和AOP(面向切面编程)的方式来实现。

下面是一个基本的步骤指南(仅供参考):

定义数据源配置

在application.properties或application.yml中定义每个数据源的连接信息。

# 主数据源 
spring.datasource.primary.url=jdbc:mysql://localhost:3306/primary_db  
spring.datasource.primary.username=root  
spring.datasource.primary.password=password  
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver  
  
 # 次数据源 
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondary_db  
spring.datasource.secondary.username=root  
spring.datasource.secondary.password=password  
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

创建数据源配置类

为每个数据源创建配置相应的DataSource。

import com.alibaba.druid.pool.DruidDataSource;
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.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源配置
 * @author zq
 * @since 14:32 2024/03/20
 **/
@Configuration
public class MoreDataSourceConfig {

    /**
     * 配置主数据源 ,用于写入数据
     * @author zq
     * @since 14:27 2024/03/20
     * @return javax.sql.DataSource
     **/
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return new DruidDataSource();
    }

    /**
     * 配置从数据源 ,用于读取数据
     * @author zq
     * @since 14:27 2024/03/20
     * @return javax.sql.DataSource
     **/
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        //return DataSourceBuilder.create().build();
        return new DruidDataSource();
    }
    // ...
}

创建动态数据源

实现一个继承自AbstractRoutingDataSource的动态数据源类,用来根据上下文切换数据源。

public class DynamicDataSource extends AbstractRoutingDataSource {  
    @Override  
    protected Object determineCurrentLookupKey() {  
        return DataSourceContextHolder.getDataSourceType();  
    }  
}

DataSourceContextHolder是一个用来保存当前线程的数据源键的工具类。

public class DataSourceContextHolder {  
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();  
  
    public static void setDataSourceType(String dataSourceType) {  
        contextHolder.set(dataSourceType);  
    }  
  
    public static String getDataSourceType() {  
        return contextHolder.get();  
    }  
  
    public static void clearDataSourceType() {  
        contextHolder.remove();  
    }  
}

注册动态数据源

在配置类中注册动态数据源,并将所有数据源设置到动态数据源中。

@Configuration  
public class DataSourceConfig {  
  
    @Autowired  
    @Qualifier("primaryDataSource")  
    private DataSource dataSourcePrimary;  
  
    @Autowired  
    @Qualifier("secondaryDataSource")  
    private DataSource dataSourceSecondary;  
  
    @Bean(name = "dataSource")  
    public DataSource dataSource() {  
        Map<Object, Object> targetDataSources = new HashMap<>();  
        targetDataSources.put("primary", dataSourcePrimary);  
        targetDataSources.put("secondary", dataSourceSecondary);  
  
        DynamicDataSource dataSource = new DynamicDataSource();  
        dataSource.setTargetDataSources(targetDataSources);  
        dataSource.setDefaultTargetDataSource(dataSourcePrimary); // 设置默认数据源  
        return dataSource;  
    }  
  
    // ... 其他配置 ...  
}

使用AOP实现数据源自动切换

通过AOP在方法执行前设置数据源,并在方法执行后清除数据源。

@Aspect  
@Component  
public class DataSourceAspect {  
  
    @Before("@annotation(dataSource)")  
    public void switchDataSource(JoinPoint point, DataSourceSwitch dataSource) {  
        DataSourceContextHolder.setDataSourceType(dataSource.value());  
    }  
  
    @After("@annotation(dataSource)")  
    public void restoreDataSource(JoinPoint point, DataSourceSwitch dataSource) {  
        DataSourceContextHolder.clearDataSourceType();  
    }  
}

DataSourceSwitch是一个自定义注解,用来标识需要切换数据源的方法。

@Target({ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface DataSourceSwitch {  
    String value() default "primary";  
}

在需要切换数据源的方法上使用注解

在需要切换数据源的服务层或DAO层方法上添加@DataSourceSwitch注解,并指定数据源类型。

@Service  
public class SomeService {  
    // 调用secondary数据源的进行读取操作  
    @DataSourceSwitch("secondary")
    public void someMethodUsingSecondaryDataSource() {
     
    }

    // 默认使用primary数据源的写入操作
    @DataSourceSwitch
    public void someMethodUsingDefaultDataSource() {  
          
    }
}

事务管理说明

如果你的业务操作需要在事务中执行,确保你的事务管理器能够处理数据源的切换。这可能需要你自定义事务管理器,或者使用AOP来在事务开始之前设置正确的数据源。

注意事项

通过以上步骤,实现了在Spring Boot中配置多个数据源,并可以通过注解的方式自动切换数据源。在调用带有@DataSourceSwitch注解的方法时,AOP切面会自动将数据源切换到指定的数据源,并在方法执行完毕后恢复默认数据源。

当使用JPA时,确保每个数据源都有自己的EntityManagerFactory和TransactionManager。
考虑到线程安全问题,确保你的数据源切换逻辑是线程安全的
数据源切换通常用于读取和写入操作分离的场景,或者当你有多个独立的数据库需要访问时。确保不要过度使用,因为它可能增加复杂性和出错的可能性。

请注意,这是一个高级功能,并且可能会引入额外的复杂性和潜在的问题。在生产环境中使用之前,请确保彻底测试你的实现