从Spring Boot连接到Heroku Postgres

Jon*_*nik 8 java postgresql spring 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

我正在使用Profiles,这似乎是我想要的最佳匹配:在Heroku SPRING_PROFILES_ACTIVE设置为postgres,而在本地开发spring.profiles.active中h2使用H2内存数据库(其配置在此省略).这种方法似乎工作正常.

在application-postgres.properties(特定于配置文件的属性)中:

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.driverClassName=org.postgresql.Driver
Run Code Online (Sandbox Code Playgroud)

DataSource来自Tomcat似乎是一个很好的选择,因为默认的依赖包括它,并且因为Spring Boot参考指南说:

我们更喜欢将Tomcat池化DataSource用于其性能和并发性,因此如果可用,我们总是选择它.

(我也看到BasicDataSourceCommons DBCP 与Spring Boot一起使用.但对我而言,这似乎不是最干净的选择,因为默认依赖项不包括Commons DBCP.而且总的来说我想知道Apache Commons是否真的可以, 2015年,成为连接Postgres的推荐方式......此外Heroku文档BasicDataSource为这种情况提供" 在Spring中";我认为这是指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>
Run Code Online (Sandbox Code Playgroud)

当前状态:失败并显示"未加载JDBC驱动程序,因为driverClassName属性为null":

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
Run Code Online (Sandbox Code Playgroud)

在日志中,我看到我postgresDataSource被称为正常,并且正在使用PostgreSQLDialect (没有它失败,"当'hibernate.dialect'未设置时,访问DialectResolutionInfo不能为空").

我的具体问题

  1. 那么,如何让这个工作?我正在设置spring.datasource.driverClassName,为什么"不加载JDBC驱动程序作为driverClassName属性为空"?
  2. 使用Tomcat是DataSource好还是你会推荐别的吗?
  3. 是否必须postgresql使用特定版本定义依赖项?(没有这个,我得到"找不到合适的驱动程序"错误.)
  4. 是否有更简单的方法来完成所有这些(同时坚持使用Java代码和/或属性;请不要使用XML)?

jon*_*ckt 20

使用 Heroku 和 Postgres 的 Spring Boot 2.x 最简单最干净的方法

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

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

大多数人希望与 Spring Boot 和 Heroku 一起使用的开发过程包括用于测试和快速开发周期的本地 H2 内存数据库 - 以及用于在 Heroku 上登台和生产的Heroku Postgres 数据库。

  • 首先是 - 您不需要为此使用 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 /sf/answers/3497909971/) -->
    <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>
Run Code Online (Sandbox Code Playgroud)

这里有一个棘手的事情是 的使用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(这并不总是需要的,因为 Spring Boot 可以从 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 实体自动创建您的表,这真的很棒 - 因为您不需要使用CREATESQL 语句或 DDL 文件)

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

heroku-spring-boot-app-postgres-config

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

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

您可能已经注意到,我们将tomcat-jdbc依赖项添加到我们的 pom.xml 并配置SPRING_DATASOURCE_TYPE=org.apache.tomcat.jdbc.pool.DataSource为环境变量。关于这句话的文档中只有一点提示

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

我切换回 Tomcat 池化数据源而不是使用 Spring Boot 2.x 标准 HikariCP 有几个原因。正如我已经在这里解释的那样,如果您不指定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
Run Code Online (Sandbox Code Playgroud)

所以我没有让 Spring Boot 2.x 使用 HikariCP 在 Heroku 和 Postgres 上工作,而是使用 Tomcat JDBC - 而且我也不想中断包含预先描述的本地 H2 数据库的开发过程。请记住:我们正在寻找使用 JPA/Hibernate 在 Spring Boot 应用程序中连接到 Heroku Postgres 的最简单、最干净的方法!

  • 然而,这不是一个永久的解决方案,因为凭证是由 Heroku 轮换的,对吗?即使在您的屏幕截图中,它也显示“请注意,这些凭据不是永久性的。” (2认同)
  • 也许你是对的,但到目前为止,Heroku 从未在我的任何 GitHub 项目中轮换这些凭据 - 特别是在 https://github.com/jonashackt/spring-boot-vuejs 中,该项目多年来运行良好。 (2认同)

Jon*_*nik 8

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

  • 正如jny所指出的,我需要明确地设置JDBC驱动程序:
    • dataSource.setDriverClassName("org.postgresql.Driver");
    • (原因是我正在定义一个自定义数据源,覆盖Spring的默认值,导致我的spring.datasource.driverClassName属性没有效果.据我所知,由于Heroku的动态特性DATABASE_URL,我需要自定义数据源才能使它工作.)
  • 在此之后连接工作,但它不稳定; org.postgresql.util.PSQLException: This connection has been closed.应用程序运行了一段时间后我开始学习.一个有点令人惊讶的解决方案(基于这个答案)是为了启用某些测试,例如testOnBorrow在Tomcat DataSource上:
    • 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

只有这个application-postgres.properties:

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
Run Code Online (Sandbox Code Playgroud)

现在,我遇到的两个问题都可能来自Tomcat(org.apache.tomcat.jdbc.pool)的DataSource.显然, BasicDataSource(Commons DBCP)有更明智的默认值.但正如问题中提到的,我宁愿使用Spring Boot默认配置的东西,特别是因为它在参考指南中得到了强烈支持.

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

使用JDBC_DATABASE_*变量代替

更新:请注意,使用JDBC_DATABASE_*比上面的内容简单得多,正如本答案所指出的那样.很长一段时间,我的印象DATABASE_URL应该是首选,但现在我不再那么肯定了.


Spi*_*der 7

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

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

JDBC_DATABASE_URL
JDBC_DATABASE_USERNAME
JDBC_DATABASE_PASSWORD
Run Code Online (Sandbox Code Playgroud)

您可能已经知道,如果Spring Boot spring.datasource.*在application.properties文件中找到属性,它将自动配置数据库。这是我的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
Run Code Online (Sandbox Code Playgroud)

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')
}
Run Code Online (Sandbox Code Playgroud)