从Spring Boot连接到Heroku Postgres

时间:2015-11-10 15:13:47

标签: java spring postgresql heroku spring-boot

我正在寻找最简单的,使用JPA / Hibernate在 Spring Boot 应用中连接到Heroku Postgres的最干净方式。

我没有在Heroku或Spring Boot文档中看到这个组合的完整示例,所以我想在Stack Overflow上记录这个。

我想尝试这样的事情:

@Configuration   
public class DataSourceConfig {

    Logger log = LoggerFactory.getLogger(getClass());

    @Bean
    @Profile("postgres")
    public DataSource postgresDataSource() {        
        String databaseUrl = System.getenv("DATABASE_URL")
        log.info("Initializing PostgreSQL database: {}", databaseUrl);

        URI dbUri;
        try {
            dbUri = new URI(databaseUrl);
        }
        catch (URISyntaxException e) {
            log.error(String.format("Invalid DATABASE_URL: %s", databaseUrl), e);
            return null;
        }

        String username = dbUri.getUserInfo().split(":")[0];
        String password = dbUri.getUserInfo().split(":")[1];
        String dbUrl = "jdbc:postgresql://" + dbUri.getHost() + ':' 
            + dbUri.getPort() + dbUri.getPath();

        // fully-qualified class name to distuinguish from javax.sql.DataSource 
        org.apache.tomcat.jdbc.pool.DataSource dataSource 
            = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setUrl(dbUrl);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

}

我正在使用Profiles,这似乎与我想要的完全匹配:在Heroku SPRING_PROFILES_ACTIVE设置为postgres,而在本地开发spring.profiles.activeh2使用H2内存数据库(此处省略其配置)。这种方法似乎运作良好。

application-postgres.propertiesprofile-specific properties)中:

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.driverClassName=org.postgresql.Driver
来自Tomcat的

DataSource似乎是一个不错的选择,因为默认依赖包含它,因为Spring Boot reference guide says

  

我们更喜欢Tomcat池化DataSource的性能和   并发,所以如果可以,我们总是选择它。

(我也看到了来自Commons DBCP being used with Spring BootBasicDataSource。但对我来说,这似乎不是最干净的选择,因为默认依赖项包括Commons DBCP。总的来说,我想知道2015年的Apache Commons是否真的,是推荐的连接Postgres的方式......另外Heroku documentation提供"在Spring" BasicDataSource对于这种情况;我认为这是指Commons DBCP,因为我在Spring中看不到这样的类。)

依赖关系:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>       
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.4-1205-jdbc42</version>
</dependency>

当前状态:使用&#34失败;未加载JDBC驱动程序,因为driverClassName属性为null&#34;:

eConfig$$EnhancerBySpringCGLIB$$463388c1 : Initializing PostgreSQL database: postgres:[...]
j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
[...]
o.a.tomcat.jdbc.pool.PooledConnection    : Not loading a JDBC driver as driverClassName property is null.    
o.a.tomcat.jdbc.pool.PooledConnection    : Not loading a JDBC driver as driverClassName property is null.
[...]
org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect

在日志中,我看到我的postgresDataSource被调用就好了 PostgreSQLDialect 正在使用(没有这个&#34;当&#39; hibernate.dialect&#39;未设置&#34;)时,对DialectResolutionInfo的访问不能为空。

我的具体问题

  1. 那么,如何让这个工作?我设置spring.datasource.driverClassName,为什么&#34;没有加载JDBC驱动程序作为driverClassName属性为null&#34;?
  2. 使用Tomcat的DataSource是好还是你会推荐其他东西?
  3. 是否必须使用specific version定义上述postgresql依赖项? (我得到了#34;找不到合适的驱动程序&#34;没有这个错误。)
  4. 是否有更简单的方法来完成所有这些(同时坚持使用Java代码和/或属性;请不要使用XML)?

7 个答案:

答案 0 :(得分:8)

为了使数据库连接正常工作(以稳定的方式),我在问题中描述的设置中缺少两件事:

  • As jny pointed out,我需要明确设置JDBC驱动程序
    • dataSource.setDriverClassName("org.postgresql.Driver");
    • (原因是我定义了一个自定义数据源,覆盖了Spring的默认值,导致我的spring.datasource.driverClassName属性无效。据我所知,由于{ {3}},我需要自定义数据源才能使其正常工作。)
  • 此后连接工作正常,但不稳定;在应用程序运行一段时间后,我开始获得org.postgresql.util.PSQLException: This connection has been closed.。有点令人惊讶的dynamic nature of Heroku's DATABASE_URL(基于solution)是为了在Tomcat DataSource上启用某些测试,例如this answer
    • dataSource.setTestOnBorrow(true); dataSource.setTestWhileIdle(true); dataSource.setTestOnReturn(true); dataSource.setValidationQuery("SELECT 1");

所以,我的DataSourceConfig的固定版本:

@Configuration
public class DataSourceConfig {

    Logger log = LoggerFactory.getLogger(getClass());

    @Bean
    @Profile("postgres")
    public DataSource postgresDataSource() {
        String databaseUrl = System.getenv("DATABASE_URL")
        log.info("Initializing PostgreSQL database: {}", databaseUrl);

        URI dbUri;
        try {
            dbUri = new URI(databaseUrl);
        }
        catch (URISyntaxException e) {
            log.error(String.format("Invalid DATABASE_URL: %s", databaseUrl), e);
            return null;
        }

        String username = dbUri.getUserInfo().split(":")[0];
        String password = dbUri.getUserInfo().split(":")[1];
        String dbUrl = "jdbc:postgresql://" + dbUri.getHost() + ':' 
                       + dbUri.getPort() + dbUri.getPath();

        org.apache.tomcat.jdbc.pool.DataSource dataSource 
            = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setUrl(dbUrl);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setTestOnBorrow(true);
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnReturn(true);
        dataSource.setValidationQuery("SELECT 1");
        return dataSource;
    }

}

只有application-postgres.properties

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

现在,我遇到的两个问题都可能是testOnBorroworg.apache.tomcat.jdbc.pool)所特有的。 DataSource from Tomcat BasicDataSource(Commons DBCP)具有更明智的默认值。但正如问题中提到的,我宁愿使用默认情况下Spring Boot附带的东西,尤其是参考指南中的Apparently

我愿意接受竞争/更简单/更好的解决方案,所以请随意发帖,特别是如果你能在问题的最后解决疑惑2-4!

使用JDBC_DATABASE_*变量

更新:请注意,使用JDBC_DATABASE_*strongly endorsed简单得多。很长一段时间我都认为应该首选DATABASE_URL,但现在我不再那么肯定了。

答案 1 :(得分:5)

最简单的Spring Boot / Heroku / Hibernate配置

除了始终存在的DATABASE_URL之外,Heroku在运行时创建了3个环境变量。他们是:

JDBC_DATABASE_URL
JDBC_DATABASE_USERNAME
JDBC_DATABASE_PASSWORD

您可能知道,如果在spring.datasource.*文件中找到application.properties属性,Spring Boot将自动配置您的数据库。这是我的application.properties

的一个例子
spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}
spring.jpa.show-sql=false
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update

Hibernate / Postgres依赖

在我的情况下,我使用Hibernate(spring-boot-starter-jpa与PostgreSQL捆绑在一起,所以我需要build.gradle中的正确依赖:

dependencies {
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile('org.postgresql:postgresql:9.4.1212')
}

答案 2 :(得分:1)

使用Heroku&amp; amp;的Spring Boot 2.x最简洁的方法Postgres的

我阅读了所有答案,但没有找到Jonik正在寻找的内容:

  

我正在寻找最简单,最干净的Heroku连接方式   使用JPA / Hibernate的Spring Boot应用程序中的Postgres

大多数人想要使用Spring Boot&amp; amp; Heroku包括一个用于测试的本地H2内存数据库。快速开发周期 - 以及Heroku Postgres database用于Heroku上的分段和生产。

  • 首先是 - 您不需要使用Spring配置文件!
  • 第二:您无需编写/更改任何代码!

让我们一步一步看看我们必须做些什么。我有一个示例项目,为Postgres提供完全可用的Heroku部署和配置 - 只是为了完整性,如果你想自己测试它:github.com/jonashackt/spring-boot-vuejs

pom.xml

我们需要以下依赖性:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- In-Memory database used for local development & testing -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>

    <!-- Switch back from Spring Boot 2.x standard HikariCP to Tomcat JDBC,
    configured later in Heroku (see https://stackoverflow.com/a/49970142/4964553) -->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-jdbc</artifactId>
    </dependency>

    <!-- PostgreSQL used in Staging and Production environment, e.g. on Heroku -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.2.2</version>
    </dependency>

这里有一个棘手的问题是使用tomcat-jdbc,但我们将在一秒钟内介绍它。

在Heroku上配置环境变量

在Heroku环境中,变量名为Config Vars您听说没错,我们所要做的就是配置环境变量!我们只需要正确的。因此请转到https://data.heroku.com/(我假设已经为您的Heroku应用程序配置了Postgres数据库,这是默认行为)。

现在点击应用程序的相应Datastore,然后切换到Settings标签。然后点击View Credentials...,它应该看起来像这样:

heroku-datastore-credentials-postgres

现在打开一个新的浏览器标签,然后转到Heroku应用程序的Settings标签。单击Reveal Config Vars并创建以下环境变量:

  • SPRING_DATASOURCE_URL = jdbc :postgres ql YourPostgresHerokuHostNameHere :5432 / YourPostgresHerokuDatabaseNameHere (介意领先jdbc:ql添加到postgres!)
  • SPRING_DATASOURCE_USERNAME = YourPostgresHerokuUserNameHere
  • SPRING_DATASOURCE_PASSWORD = YourPostgresHerokuPasswordHere
  • SPRING_DATASOURCE_DRIVER-CLASS-NAME = org.postgresql.Driver(这并不总是需要since Spring Boot can deduce it for most databases from the url,只是为了完整性)
  • SPRING_JPA_DATABASE-PLATFORM = org.hibernate.dialect.PostgreSQLDialect
  • SPRING_DATASOURCE_TYPE = org.apache.tomcat.jdbc.pool.DataSource
  • SPRING_JPA_HIBERNATE_DDL-AUTO = update(这将根据您的JPA实体自动创建表格,这非常棒 - 因为您不需要跨越CREATE SQL语句或DDL文件)

在Heroku中,这应该是这样的:

heroku-spring-boot-app-postgres-config

现在就是你所要做的一切!每次更改配置变量时都会重启你的Heroku应用程序 - 所以你的应用程序现在应该在本地运行H2,并且应该准备好与PostgreSQL连接时部署在Heroku上。

如果你问:为什么我们配置Tomcat JDBC而不是Hikari

您可能已经注意到,我们将tomcat-jdbc依赖项添加到我们的pom.xml中,并将SPRING_DATASOURCE_TYPE=org.apache.tomcat.jdbc.pool.DataSource配置为环境变量。只有a slight hint in the docs about this

  

您可以完全绕过该算法并指定连接   通过设置spring.datasource.type属性来使用的池。这是   如果您在Tomcat容器中运行应用程序,这一点尤为重要......

有几个原因我切换回Tomcat汇集DataSource而不是使用Spring Boot 2.x标准HikariCP。正如我already explained here,如果你没有指定spring.datasource.url,Spring会尝试自动装入嵌入式内存H2数据库,而不是我们的PostgreSQL数据库。 Hikari的问题是,它只支持spring.datasource.jdbc-url

其次,如果我尝试使用Hikari所示的Heroku配置(因此省略SPRING_DATASOURCE_TYPE并将SPRING_DATASOURCE_URL更改为SPRING_DATASOURCE_JDBC-URL),我会遇到以下异常:

Caused by: java.lang.RuntimeException: Driver org.postgresql.Driver claims to not accept jdbcUrl, jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE

所以我没有让Spring Boot 2.x在Heroku&amp; amp;使用HikariCP进行Postgres,但使用Tomcat JDBC - 我也不想制定包含前面描述的本地H2数据库的开发过程。记住:我们正在寻找使用JPA / Hibernate在Spring Boot应用程序中连接到Heroku Postgres的最简单,最干净的方法!

答案 3 :(得分:0)

尝试使用JDBC_DATABASE_URL作为spring.datasource.url,而不是解析DATABASE_URL。

建议解析DATABASE_URL,但是如果你不能使它工作,那么JDBC_DATABASE_URL应该没问题。

答案 4 :(得分:0)

这是使用Heroku提供的示例Java应用程序Google搜索Postgres问题的最佳答案。

这些是我为使其发挥作用而采取的步骤(Win 7)。

1。)生产服务器application.properties文件将包含系统环境(确保已提交此文件)

spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}

2。)现在做git update-index --assume-unchanged .\src\main\resources\application.properties

3.。)将本地application.properties更改为硬编码。您可以通过运行heroku run env

来查看原始值
spring.datasource.url=jdbc://..
spring.datasource.username=XYZ
spring.datasource.password=ABC

这是我必须获取我的应用程序的本地副本才能工作的内容。如果有人找到更好的方法请分享!

答案 5 :(得分:0)

@Configuration
@Component
public class HerokuConfigCloud {

private static final Logger logger = 
LoggerFactory.getLogger(HerokuConfigCloud .class);

@Bean()
//@Primary this annotation to be used if more than one DB Config was used.  In that case,
// using @Primary would give precedence to a the particular "primary" config class
@Profile("heroku")
public DataSource dataSource(
        @Value("${spring.datasource.driverClassName}") final String driverClass,
        @Value("${spring.datasource.url}") final String jdbcUrl,
        @Value("${spring.datasource.username}") final String username,
        @Value("${spring.datasource.password}") final String password
        ) throws URISyntaxException {


    return DataSourceBuilder
            .create()
            .username(username)
            .password(password)
            .url(url)
            .driverClassName(driverClass)
            .build();
    }
}

答案 6 :(得分:0)

我建立了一个库来简化此过程:https://github.com/vic-cw/heroku-postgres-helper

如果您需要同时在构建脚本和应用程序逻辑中访问数据库,这将更加有用。了解为什么here

用法:

build.gradle:

// If using connection string in build script:
buildscript {
    repositories {
        maven { url 'https://jitpack.io' }
    }
    dependencies {
        classpath 'com.github.vic-cw:heroku-postgres-helper:0.1.0'
    }
}
import com.github.viccw.herokupostgreshelper.HerokuPostgresHelper;

// Use connection string in build script:
flyway {
    url = HerokuPostgresHelper.getDatabaseJdbcConnectionString()
    driver = 'org.postgresql.Driver'
}

// If using connection string inside application logic:
repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    compile group: 'com.github.vic-cw', name: 'heroku-postgres-helper', version: '0.1.0'
}

Java应用程序代码:

import com.github.viccw.herokupostgreshelper.HerokuPostgresHelper;

...

String databaseConnectionString = HerokuPostgresHelper.getDatabaseJdbcConnectionString();