Spring Boot中如何在application.properties配置多数据源并切换至第二数据源
兄弟,刚从JEE转Spring碰到多数据源配置的问题太正常了!你之前用spring.datasourceB.datasource.meta-datas的写法不对,Spring根本识别不了这种自定义前缀,我给你一步步讲清楚怎么正确配置多数据源,以及怎么切换到第二数据源:
第一步:在application.properties里正确配置多数据源
我们需要给每个数据源设置独立的可识别前缀,比如用primary和secondary区分主、次数据源,这样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+自定义注解实现:
- 先定义一个标记注解:
import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface UseSecondaryDataSource { }
- 写一个数据源上下文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(); } }
- 写动态数据源类,继承Spring的
AbstractRoutingDataSource:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 从上下文Holder里拿当前线程要用到的数据源名称 return DataSourceContextHolder.getDataSourceName(); } }
- 修改之前的数据源配置类,注册动态数据源作为主数据源:
@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; } }
- 写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(); } }
- 最后用的时候,给需要切换的方法加注解就行:
@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




