Spring Boot:如何外部化 JDBC 数据源配置?

Posted

技术标签:

【中文标题】Spring Boot:如何外部化 JDBC 数据源配置?【英文标题】:Spring Boot: How to externalize JDBC datasource configuration? 【发布时间】:2013-12-01 09:31:37 【问题描述】:

我有以下可用的 Spring Boot 控制器代码。 (部分敏感文字被替换)

package com.sample.server;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class DetailReportController

    @RequestMapping(value="/report/detail", method=RequestMethod.GET)
    public List<UFGroup> detailReport()
    
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("net.sourceforge.jtds.jdbc.Driver");
        dataSource.setUrl("jdbc:jtds:sqlserver://111.11.11.11/DataBaseName;user=sa;password=password");

        JdbcTemplate jt = new JdbcTemplate(dataSource);

        List<UFGroup> results = jt.query(
            "select NID, SCode, SName from UFGroup",
            new RowMapper<UFGroup>()
            
                @Override
                public UFGroup mapRow(ResultSet rs, int rowNum) throws SQLException
                
                    return new UFGroup(rs.getInt("NID"), rs.getString("SCode"),
                            rs.getString("SName"));
                
            );

        return results;
    

    private static class UFGroup
    
        public int nid;
        public String scode;
        public String sname;

        public UFGroup(int nid, String scode, String sname)
        
            this.nid = nid;
            this.scode = scode;
            this.sname = sname;
        
    

现在我想外部化数据源的配置。 即BasicDataSource类、驱动类名、数据源URL应该放在application.properties中。 我该怎么做?

顺便说一句,我是 Spring、Spring Boot 甚至 Java Beans 的新手。我所拥有的只是一些 Java 编程经验,主要针对移动设备。我花了几天时间研究 Spring Boot 环境,但我真的不知所措。 所以,请用具体的例子给我确切的说明。

更新: 当我应用 M. Deinum 的答案时,运行应用程序时出现以下错误:

2013-11-18 19:37:54.789  INFO 6868 --- [           main] com.logicplant.uflow.server.Application  : Starting Application on zeo-PC with PID 6868 (C:\Projects\uFlow\Dev\Server\Spring\uFlowServer\build\libs\uFlowServer-1.0.0.jar started by zeo)
2013-11-18 19:37:54.830  INFO 6868 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@7f83698f: startup date [Mon Nov 18 19:37:54 KST 2013]; root of context hierarchy
2013-11-18 19:37:55.931  INFO 6868 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2013-11-18 19:37:55.932  INFO 6868 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.42
2013-11-18 19:37:56.009  INFO 6868 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2013-11-18 19:37:56.010  INFO 6868 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1183 ms
2013-11-18 19:37:56.165  INFO 6868 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2013-11-18 19:37:56.165  INFO 6868 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2013-11-18 19:37:56.242  INFO 6868 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-11-18 19:37:56.388  INFO 6868 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "[/report/detail],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]" onto public java.util.List<com.logicplant.uflow.server.DetailReportController$UFGroup> com.logicplant.uflow.server.DetailReportController.detailReport()
2013-11-18 19:37:56.438  INFO 6868 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-11-18 19:37:56.439  INFO 6868 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-11-18 19:37:56.788  INFO 6868 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 622 ms
2013-11-18 19:37:56.881  INFO 6868 --- [           main] o.apache.catalina.core.StandardService   : Stopping service Tomcat
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:53)
    at java.lang.Thread.run(Unknown Source)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'detailReportController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.jdbc.core.JdbcTemplate com.logicplant.uflow.server.DetailReportController.jt; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.jdbc.core.JdbcTemplate] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: @org.springframework.beans.factory.annotation.Autowired(required=true)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1139)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:295)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:665)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:509)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:278)
    at com.logicplant.uflow.server.Application.main(Application.java:17)
    ... 6 more
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.jdbc.core.JdbcTemplate com.logicplant.uflow.server.DetailReportController.jt; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.jdbc.core.JdbcTemplate] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: @org.springframework.beans.factory.annotation.Autowired(required=true)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:505)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
    ... 20 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.jdbc.core.JdbcTemplate] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: @org.springframework.beans.factory.annotation.Autowired(required=true)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1051)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:919)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:820)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:477)
    ... 22 more

作为参考,build.grade 文件(我使用 Gradle)的内容如下:(根据 M. Deinum 的建议,我删除了对 org.apache.commons.dbcp 的依赖。)

buildscript 
    repositories 
        maven  url "http://repo.spring.io/libs-snapshot" 
        mavenLocal()
    
    dependencies 
        classpath("org.springframework.boot:spring-boot-gradle-plugin:0.5.0.M5")
    


apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar 
    baseName = 'uFlowServer'
    version = '1.0.0'


repositories 
    mavenCentral()
    maven  url "http://repo.spring.io/libs-snapshot" 


dependencies 
    compile("org.springframework.boot:spring-boot-starter-web:0.5.0.M5")
    compile("com.fasterxml.jackson.core:jackson-databind")
    compile("org.springframework:spring-jdbc:4.0.0.M3")
    runtime("net.sourceforge.jtds:jtds:1.3.1")
    testCompile("junit:junit:4.11")


task wrapper(type: Wrapper) 
    gradleVersion = '1.8'

这是Application.java文件,它是主要的源文件。

package com.sample.server;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application

    public static void main(String args[])
    
        SpringApplication app = new SpringApplication(Application.class);
        app.setShowBanner(false);
        app.run(args);
    

有什么办法可以解决这个错误?

更新: 正如 M. Deinum 所建议的,当我按如下方式更改 build.gradle 文件时,应用程序工作正常!

buildscript 
    repositories 
        maven  url "http://repo.spring.io/libs-snapshot" 
        mavenLocal()
    
    dependencies 
        classpath("org.springframework.boot:spring-boot-gradle-plugin:0.5.0.M6")
    


apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar 
    baseName = 'uFlowServer'
    version = '1.0.0'


repositories 
    mavenCentral()
    maven  url "http://repo.spring.io/libs-snapshot" 


dependencies 
    compile("org.springframework.boot:spring-boot-starter-web:0.5.0.M6")
    compile("org.springframework.boot:spring-boot-starter-jdbc:0.5.0.M6")
    compile("com.fasterxml.jackson.core:jackson-databind")
    runtime("net.sourceforge.jtds:jtds:1.3.1")
    testCompile("junit:junit:4.11")


task wrapper(type: Wrapper) 
    gradleVersion = '1.8'

【问题讨论】:

【参考方案1】:

将您的控制器更改为以下内容

@RestController
public class DetailReportController 

    @Autowired
    private JdbcTemplate jt;

    @RequestMapping(value="/report/detail", method=RequestMethod.GET)
    public List<UFGroup> detailReport() 
        List<UFGroup> results = jt.query(
            "select NID, SCode, SName from UFGroup",
            new RowMapper<UFGroup>()
                @Override
                public UFGroup mapRow(ResultSet rs, int rowNum) throws SQLException 
                    return new UFGroup(rs.getInt("NID"), rs.getString("SCode"), rs.getString("SName"));
                
            );
        return results;
    

    private static class UFGroup
    
        public int nid;
        public String scode;
        public String sname;

        public UFGroup(int nid, String scode, String sname)
        
            this.nid = nid;
            this.scode = scode;
            this.sname = sname;
        
    

src/main/resources 中添加application.properties 并带有以下内容

spring.datasource.driverClassName=net.sourceforge.jtds.jdbc.Driver
spring.datasource.url=jdbc:jtds:sqlserver://111.11.11.11/DataBaseName
spring.datasource.username=sa
spring.datasource.password=password

然后简单地启动您的应用程序。不需要xml。 Spring boot 将创建 DataSource 并将添加一个默认的 JdbcTemplate 实例。

提示:删除对 org.apache.commons.dbcp 的依赖项 spring-boot 将为您提供更新(恕我直言更好)的 tomcat 连接池(尽管名称可以完全独立使用)。

【讨论】:

感谢您的回答。但是当我应用它时,发生了错误。我已编辑问题以包含错误输出。 我假设您已经拥有所有依赖项,将 spring-boot-starter-jdbc 添加为依赖项。同时删除spring-jdbc 依赖,因为它会被spring-boot-starter-jdbc 引入。另一个建议更新为 M6 而不是 Spring Boot 的 M5 非常感谢。你救了我的命!当我应用您的两个建议时(包括 spring-boot-starter-jdbc 依赖项和更新到 M6),应用程序工作。再次感谢你。作为参考,我更新了我的问题以包含 build.gradle 文件的最终(更正)内容。【参考方案2】:

我会以更好的方式重新修改您的代码。

首先不需要在代码中使用 new 运算符,因为您使用的是 Spring,因此您可以使用 Spring 的强大功能,即依赖注入。

@RestController
public class DetailReportController
       @Required
            private JdbcTemplate jt;

            //setter for the same

            @RequestMapping(value="/report/detail", method=RequestMethod.GET)
    public List<UFGroup> detailReport()
    

        List<UFGroup> results = jt.query(
            "select NID, SCode, SName from UFGroup",
            new RowMapper<UFGroup>()
            
                @Override
                public UFGroup mapRow(ResultSet rs, int rowNum) throws SQLException
                
                    return new UFGroup(rs.getInt("NID"), rs.getString("SCode"),
                            rs.getString("SName"));
                
            );

        return results;
    

    private static class UFGroup
    
        public int nid;
        public String scode;
        public String sname;

        public UFGroup(int nid, String scode, String sname)
        
            this.nid = nid;
            this.scode = scode;
            this.sname = sname;
        
    

应用程序上下文.xml

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="$jdbc.driverClassName"/>
        <property name="url" value="$jdbc.url"/>
        <property name="username" value="$jdbc.username"/>
        <property name="password" value="$jdbc.password"/>
        <property name="maxActive" value="100"/>
        <property name="maxIdle" value="30"/>
        <property name="maxWait" value="16000"/>
        <property name="minIdle" value="0"/>
    </bean>

   <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <property name="location">
        <value>database.properties</value>
    </property>
</bean>


    <bean id="jt" class="org.springframework.jdbc.core.JdbcTemplate;">
         <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="detailReportController" class="your class">
         <property name="jt" ref="jt"/>
    </bean>

另一个建议您可以在 DAO 类中移动与数据库相关的代码,在 Controller 中使用相同的代码是非常糟糕的做法。否则,如果您想坚持自己的方法,请使用 java.util 包的 Properties 类

See here

【讨论】:

感谢您的快速回复。但是我读到如果使用 Spring Boot,则不再需要 xml 配置文件,我可以使用 application.properties 文件来覆盖一些默认配置值。我还需要你建议的 xml 文件吗? 如果您使用 Spring,则无法完全消除 xml 文件并检查更新的答案,如果您认为合适,请接受我的答案。 再次感谢您,我会研究您的答案。由于我是新手,可能需要一些时间...... @ShoaibChikate 这不是真的(甚至半年前还没有),您可以在使用 Spring 时完全消除 XML 文件。在 Servlet 3.0+ 环境中运行时,甚至不需要 web.xml。 ;)

以上是关于Spring Boot:如何外部化 JDBC 数据源配置?的主要内容,如果未能解决你的问题,请参考以下文章

单体Spring boot引入外部配置文件yml,properties

TZ_11_Spring-Boot的属性注入方式(jdbc为例)

如何在具有 JDBC 安全性的 Spring Boot 中使用 Flyway?

Spring Boot - 构建数据访问层

spring-boot:数据库中断后jdbc重新连接

如何在 Spring Boot linux 中向类路径提供外部配置资源(数据库属性和 XML 文件)