You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot中如何在application.properties配置多数据源并切换至第二数据源

兄弟,刚从JEE转Spring碰到多数据源配置的问题太正常了!你之前用spring.datasourceB.datasource.meta-datas的写法不对,Spring根本识别不了这种自定义前缀,我给你一步步讲清楚怎么正确配置多数据源,以及怎么切换到第二数据源:

第一步:在application.properties里正确配置多数据源

我们需要给每个数据源设置独立的可识别前缀,比如用primarysecondary区分主、次数据源,这样Spring就不会报“未知属性”的错误了:

# 主数据源(保留你原来的基础配置,只是加了.primary前缀)
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.primary.url=jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
spring.datasource.primary.username=root
spring.datasource.primary.password=123456

# 第二数据源
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db2?useSSL=false&serverTimezone=UTC
spring.datasource.secondary.username=root
spring.datasource.secondary.password=123456
第二步:编写配置类注册数据源

光在配置文件写还不够,得写个配置类把这些属性绑定到DataSource实例里,还要指定主数据源(不然Spring启动会懵,不知道默认用哪个):

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;

@Configuration
public class MultipleDataSourceConfig {

    // 主数据源,必须加@Primary注解标记默认数据源
    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    // 第二数据源
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

这里的@ConfigurationProperties(prefix = "...")就是告诉Spring:去配置文件里找对应前缀的属性,自动填充到这个DataSource对象里。

第三步:切换使用第二数据源(两种常用方式)

方式一:固定指定数据源(适合场景固定的情况)

如果你只是在某个DAO/Service里固定用第二数据源,直接用@Qualifier或者@Resource指定数据源的Bean名称就行:
比如用JdbcTemplate的例子:

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository
public class SecondaryDbRepo {

    // 直接注入第二数据源
    @Resource(name = "secondaryDataSource")
    private DataSource secondaryDataSource;

    public void querySecondaryData() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(secondaryDataSource);
        String sql = "SELECT * FROM your_table_in_db2";
        // 执行SQL并处理结果
        jdbcTemplate.query(sql, (rs, rowNum) -> rs.getString("column_name"));
    }
}

如果用MyBatis,就给不同数据源的Mapper分包,然后在配置类里绑定对应的SqlSessionFactory:

// 主数据源的MyBatis配置
@Configuration
@MapperScan(basePackages = "com.yourproject.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryMyBatisConfig {

    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

    @Primary
    @Bean(name = "primarySqlSessionFactory")
    public SqlSessionFactory primarySqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(primaryDataSource);
        // 配置mapper.xml路径等MyBatis属性
        return bean.getObject();
    }
}

// 第二数据源的MyBatis配置
@Configuration
@MapperScan(basePackages = "com.yourproject.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryMyBatisConfig {

    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Bean(name = "secondarySqlSessionFactory")
    public SqlSessionFactory secondarySqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(secondaryDataSource);
        // 配置mapper.xml路径等MyBatis属性
        return bean.getObject();
    }
}

这样primary包下的Mapper自动用主数据源,secondary包下的自动用第二数据源,不用手动指定。

方式二:动态切换数据源(适合运行时按需切换的场景)

如果需要在代码里动态切换主、次数据源,可以用AOP+自定义注解实现:

  1. 先定义一个标记注解:
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseSecondaryDataSource {
}
  1. 写一个数据源上下文Holder,存储当前线程用的数据源名称:
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceName(String dataSourceName) {
        contextHolder.set(dataSourceName);
    }

    public static String getDataSourceName() {
        return contextHolder.get();
    }

    public static void clearDataSourceName() {
        contextHolder.remove();
    }
}
  1. 写动态数据源类,继承Spring的AbstractRoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 从上下文Holder里拿当前线程要用到的数据源名称
        return DataSourceContextHolder.getDataSourceName();
    }
}
  1. 修改之前的数据源配置类,注册动态数据源作为主数据源:
@Configuration
public class MultipleDataSourceConfig {

    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 设置默认用主数据源
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
        // 把两个数据源都放进动态数据源的映射里
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("primary", primaryDataSource());
        dataSourceMap.put("secondary", secondaryDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }
}
  1. 写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.yourproject.annotation.UseSecondaryDataSource)")
    public void switchToSecondary() {
        DataSourceContextHolder.setDataSourceName("secondary");
    }

    // 方法执行完恢复默认数据源
    @After("@annotation(com.yourproject.annotation.UseSecondaryDataSource)")
    public void restoreDataSource() {
        DataSourceContextHolder.clearDataSourceName();
    }
}
  1. 最后用的时候,给需要切换的方法加注解就行:
@Service
public class DataService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 默认用主数据源
    public List<String> getPrimaryData() {
        return jdbcTemplate.queryForList("SELECT name FROM table_in_db1", String.class);
    }

    // 加注解后自动切换到第二数据源
    @UseSecondaryDataSource
    public List<String> getSecondaryData() {
        return jdbcTemplate.queryForList("SELECT name FROM table_in_db2", String.class);
    }
}
几个关键注意点
  • 一定要给主数据源加@Primary注解,不然Spring启动时会因为找不到默认数据源而报错;
  • 配置文件的前缀要和配置类里@ConfigurationProperties的prefix完全一致,不然读不到配置;
  • 用MyBatis时,不同数据源的Mapper尽量分开放,避免混淆。

内容的提问来源于stack exchange,提问作者user9137315

火山引擎 最新活动