You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用DataSourceLookup获取Spring Boot application.yml中配置的命名DataSource

Spring Boot 多数据源动态选择(非JPA、无JNDI)

需求明确:在Spring Boot REST接口中,根据请求传入的databaseName参数动态选择对应命名的数据源执行JDBC查询;要求在application.yml中配置多数据源,不依赖容器级JNDI,且连接池支持高并发。

原application.yml配置:

spring:
  datasource:
    source1:
      driver-class-name: com.mysql.cj.jdbc.Driver  # 注意:mysql-connector-java 8+ 需使用该驱动类
      url: jdbc:mysql://dbserver.local:3306/source1?useSSL=false&serverTimezone=UTC
      username: myuser
      password: mypass
    source2:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://dbserver.local:3306/source2?useSSL=false&serverTimezone=UTC
      username: myuser
      password: mypass

之前的错误原因

1. JndiDataSourceLookup的问题

你最初使用的JndiDataSourceLookup是为容器级JNDI数据源设计的,而独立运行的Spring Boot应用没有容器提供的JNDI上下文,执行时抛出如下错误:

javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or in an application resource file: java.naming.factory.initial
    at java.naming/javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:702) ~[na:na]
    at java.naming/javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:305) ~[na:na]
    at java.naming/javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:342) ~[na:na]
    at java.naming/javax.naming.InitialContext.lookup(InitialContext.java:409) ~[na:na]
    at org.springframework.jndi.JndiTemplate.lambda$lookup$0(JndiTemplate.java:157) ~[spring-context-5.3.30.jar:5.3.30]
    at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:92) ~[spring-context-5.3.30.jar:5.3.30]
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:157) ~[spring-context-5.3.30.jar:5.3.30]
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179) ~[spring-context-5.3.30.jar:5.3.30]
    at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:105) ~[spring-context-5.3.30.jar:5.3.30]
    at org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup.getDataSource(JndiDataSourceLookup.java:45) ~[spring-jdbc-5.3.30.jar:5.3.30]

该方案完全不符合“无需配置容器级JNDI资源”的需求。

2. BeanFactoryDataSourceLookup的问题

你后来尝试的BeanFactoryDataSourceLookup是从Spring容器中查找Bean,但Spring Boot不会自动将spring.datasource.source1这类嵌套配置转化为DataSourceBean,执行时抛出:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'source1' available

根本原因是source1对应的DataSourceBean未注册到Spring容器中。


正确实现方案

1. 选择高并发连接池

Spring Boot默认集成的HikariCP是当前性能最优的连接池,天生支持高并发,只需引入spring-boot-starter-jdbc依赖即可自动启用。

2. 编写配置类,注册多数据源Bean

手动读取yml中的多数据源配置,将每个数据源封装为HikariDataSource并注册为Spring Bean,Bean名称与yml中的数据源名称(source1、source2)保持一致。

@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
public class MultiDataSourceConfig {

    private Map<String, DataSourceProperties> dataSources = new HashMap<>();

    // 读取yml中spring.datasource下的所有子配置
    public Map<String, DataSourceProperties> getDataSources() {
        return dataSources;
    }

    public void setDataSources(Map<String, DataSourceProperties> dataSources) {
        this.dataSources = dataSources;
    }

    // 为每个数据源创建并注册Bean
    @Bean
    public Map<String, DataSource> dataSourceMap() {
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) ApplicationContextProvider.getApplicationContext();

        for (Map.Entry<String, DataSourceProperties> entry : dataSources.entrySet()) {
            String dsName = entry.getKey();
            DataSourceProperties properties = entry.getValue();

            // 使用HikariCP构建数据源
            HikariDataSource dataSource = properties.initializeDataSourceBuilder()
                    .type(HikariDataSource.class)
                    .build();

            // 可额外配置HikariCP参数,适配高并发场景
            dataSource.setMaximumPoolSize(20);
            dataSource.setConnectionTimeout(30000);

            dataSourceMap.put(dsName, dataSource);
            // 将数据源注册为Spring Bean,名称对应yml中的数据源名
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HikariDataSource.class);
            builder.addPropertyValue("driverClassName", dataSource.getDriverClassName());
            builder.addPropertyValue("jdbcUrl", dataSource.getJdbcUrl());
            builder.addPropertyValue("username", dataSource.getUsername());
            builder.addPropertyValue("password", dataSource.getPassword());
            builder.addPropertyValue("maximumPoolSize", dataSource.getMaximumPoolSize());
            builder.addPropertyValue("connectionTimeout", dataSource.getConnectionTimeout());
            registry.registerBeanDefinition(dsName, builder.getBeanDefinition());
        }
        return dataSourceMap;
    }
}

配套ApplicationContext工具类,用于获取上下文注册Bean:

@Component
public class ApplicationContextProvider implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        applicationContext = context;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

3. 控制器中动态获取数据源

现在可以直接通过ApplicationContext根据名称获取对应的DataSourceBean,或使用BeanFactoryDataSourceLookup(此时Bean已存在,可正常查找)。

方式一:直接通过ApplicationContext获取

@RestController
public class SQLDataController {
    private static final Logger log = LoggerFactory.getLogger(SQLDataController.class);

    private final ApplicationContext applicationContext;

    @Autowired
    public SQLDataController(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @GetMapping(value = "/csv/**", produces = "text/csv")
    public void streamCsv(@RequestParam String databaseName) throws Exception {
        // 根据参数获取对应数据源
        DataSource dataSource = applicationContext.getBean(databaseName, DataSource.class);
        try (Connection conn = dataSource.getConnection()) {
            // 执行JDBC查询、生成CSV输出等逻辑
            // ...
        } catch (SQLException e) {
            log.error("获取数据库连接失败", e);
            throw new RuntimeException("数据库操作异常", e);
        }
    }
}

方式二:使用BeanFactoryDataSourceLookup

@RestController
public class SQLDataController {
    private static final Logger log = LoggerFactory.getLogger(SQLDataController.class);

    private final DataSourceLookup dataSourceLookup;

    @Autowired
    public SQLDataController(BeanFactory beanFactory) {
        this.dataSourceLookup = new BeanFactoryDataSourceLookup(beanFactory);
    }

    @GetMapping(value = "/csv/**", produces = "text/csv")
    public void streamCsv(@RequestParam String databaseName) throws Exception {
        DataSource dataSource = dataSourceLookup.getDataSource(databaseName);
        try (Connection conn = dataSource.getConnection()) {
            // 执行JDBC操作逻辑
            // ...
        } catch (SQLException e) {
            log.error("数据库操作失败", e);
            throw new RuntimeException("数据库操作异常", e);
        }
    }
}

关键说明

  • 驱动类适配:mysql-connector-java 8.x版本必须使用com.mysql.cj.jdbc.Driver,旧驱动类com.mysql.jdbc.Driver已废弃。
  • 连接池参数配置:HikariCP的参数可直接在yml中针对每个数据源配置,例如:
    spring:
      datasource:
        source1:
          hikari:
            maximum-pool-size: 25
            connection-timeout: 20000
    
  • Bean名称一致性:必须确保注册的DataSource Bean名称与yml中的数据源名称完全一致,才能实现动态匹配。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 17:34:51