从 Spring Boot 连接到 Heroku Postgres

Posted

技术标签:

【中文标题】从 Spring Boot 连接到 Heroku Postgres【英文标题】:Connecting to Heroku Postgres from Spring Boot 【发布时间】:2016-02-11 12:32:27 【问题描述】:

我正在寻找使用 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 in-内存数据库(此处省略其配置)。这种方法似乎效果很好。

application-postgres.properties (profile-specific properties):

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.driverClassName=org.postgresql.Driver

来自 Tomcat 的DataSource 似乎是一个不错的选择,因为默认依赖项包含它,并且因为 Spring Boot reference guide says:

我们更喜欢 Tomcat 池化数据源,因为它的性能和 并发,所以如果它可用,我们总是选择它。

(我还看到了来自 Commons DBCP being used with Spring Boot 的 BasicDataSource。但对我来说,这似乎不是最干净的选择,因为默认依赖项包括 Commons DBCP。总的来说我想知道 Apache Commons 是否可以在 2015 年真的成为连接到 Postgres 的推荐方式......此外,Heroku documentation 为这种场景提供“BasicDataSource in 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>

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

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 is 正在使用中(没有这个,它会失败,“当 'hibernate.dialect' 未设置时访问 DialectResolutionInfo 不能为空”)。

我的具体问题

    那么,如何让它工作?我am设置了spring.datasource.driverClassName,那么为什么“未将 JDBC 驱动程序加载为 driverClassName 属性为空”? 使用 Tomcat 的DataSource 好吗?或者你会推荐其他的吗? 是否必须像上面那样使用specific version 定义postgresql 依赖项? (如果没有这个,我会收到“找不到合适的驱动程序”错误。) 有没有更简单的方法来完成这一切(同时坚持 Java 代码和/或属性;请不要使用 XML)?

【问题讨论】:

只是好奇,Heroku postgress 有什么特别之处,以至于您不能使用标准的 spring-boot DataSource 配置并且有 2 个属性文件用于 2 个带有连接详细信息的配置文件? @jny:我不知道;也许没有真正的理由在代码中定义 DataSource。我在Heroku docs 的带领下尝试了这样的事情。我正在寻找最简单、最干净的方法来让它工作,所以如果你知道那是什么,请发布答案:) 我没有使用 Heroku 的经验,但连接字符串似乎没有什么特别之处。您已经在使用 jpa starter,它负责数据源和 Tomcat 连接池和配置。我会从这里开始:docs.spring.io/spring-boot/docs/current/reference/html/… 看看它是否有效。 嗯,关于我知道 Heroku 的 DATABASE_URL 的一件事是 Heroku 会自动设置它,我应该从 env 变量中读取并使用整个内容,并且 not 采取和将其(部分)存储在我的代码或配置中。 It can change under some circumstances。鉴于此,我不确定如何使用spring.datasource.url 等(所有Heroku examples 读取并拆分DATABASE_URL env 变量。) 由于要覆盖数据源配置,所以需要明确设置jdbc驱动类... 【参考方案1】:

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

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

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

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

首先,您不需要为此使用 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://***.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 应用程序的 Settings 选项卡。点击Reveal Config Vars 并创建以下环境变量:

SPRING_DATASOURCE_URL = jdbc:postgresql://YourPostgresHerokuHostNameHere:5432/YourPostgresHerokuDatabaseNameHere(注意前面的 @987654342 @ 和 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 应用程序都会重新启动 - 因此您的应用程序现在应该在本地运行 H2,并且应该准备好与 PostgreSQL 连接部署在 Heroku 上。

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

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

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

我切换回 Tomcat 池数据源而不是使用 Spring Boot 2.x 标准 HikariCP 有几个原因。正如我already explained here 一样,如果您不指定spring.datasource.url,Spring 将尝试自动连接嵌入式 im-memory 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 使用 HikariCP 在 Heroku 和 Postgres 上工作,而是使用 Tomcat JDBC - 我也不想中断包含预先描述的本地 H2 数据库的开发过程。请记住:我们正在寻找使用 JPA/Hibernate 在 Spring Boot 应用程序中连接到 Heroku Postgres 的最简单、最干净的方法!

【讨论】:

这不是一个永久的解决方案,因为 Heroku 会轮换凭据,对吧?即使在您的屏幕截图中,它也会显示“请注意,这些凭据不是永久性的。” 也许你是对的,但直到现在 Heroku 从未在我的任何 GitHub 项目中轮换这些凭据 - 特别是在 github.com/jonashackt/spring-boot-vuejs 中,它已经运行多年了。 很好的答案,详细的解释和分步说明。感谢您的努力! 好文章!很简单! SPRING_DATASOURCE_URL 在解释中缺少两个斜杠//。在屏幕截图中它在那里。请修复,因为这给我带来了一些故障排除时间,并且应该是一致的。 这个家伙@jonashackt 几乎涵盖了我想对 Jonik 说的所有内容。感谢您详尽的回答!【参考方案2】:

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

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

JDBC_DATABASE_URL
JDBC_DATABASE_USERNAME
JDBC_DATABASE_PASSWORD

您可能知道,如果 Spring Boot 在您的 application.properties 文件中找到 spring.datasource.* 属性,它将自动配置您的数据库。这是我的 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(与 PostgreSQL 捆绑在 spring-boot-starter-jpa 中,所以我需要在我的 build.gradle 中使用正确的依赖项:

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

【讨论】:

是的,我也使用了JDBC_DATABASE_URL(不过还有其他技术堆栈),似乎工作正常。正如Heroku docs 现在所说:“数据库URL 的权威来源仍然是DATABASE_URL 环境变量,但JDBC_DATABASE_URL 在大多数情况下都可以使用。” 更高版本的 Spring 甚至不在乎。只需省略数据库 url、用户名和密码,Spring 会自动从环境变量中获取。【参考方案3】:

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

As jny pointed out,我需要明确设置 JDBC 驱动程序dataSource.setDriverClassName("org.postgresql.Driver"); (原因是我定义了一个自定义数据源,覆盖了 Spring 的默认值,导致我的spring.datasource.driverClassName 属性无效。据我了解,由于dynamic nature of Heroku's DATABASE_URL,我需要自定义数据源来让它发挥作用。) 此后连接正常,但不稳定;在应用程序运行一段时间后,我开始收到org.postgresql.util.PSQLException: This connection has been closed.。一个有点令人惊讶的solution(基于this answer)是在Tomcat 数据源上启用某些测试,例如testOnBorrowdataSource.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

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

我对竞争/更简单/更好的解决方案持开放态度,因此请随时发布,特别是如果您可以解决问题末尾的 2-4 问题!

改用JDBC_DATABASE_* 变量

更新:请注意,使用JDBC_DATABASE_* 比上面的要简单得多,如pointed out in this answer。很长一段时间我都认为DATABASE_URL应该是首选,但现在我不再那么确定了。

【讨论】:

【参考方案4】:

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

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

【讨论】:

谢谢。在 DataSource 中配置了更多内容后,我确实得到了 DATABASE_URL 的工作;见***.com/a/33682249/56285。 别忘了JDBC_DATABASE_USERNAMEJDBC_DATABASE_PASSWORD【参考方案5】:

这是使用 Heroku 提供的示例 Java 应用程序在谷歌上搜索 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

这是我必须让我的应用程序的本地副本工作。如果有人找到更好的方法,请分享!

【讨论】:

【参考方案6】:
@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();
    

【讨论】:

【参考方案7】:

我构建了一个库来简化此操作: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();

【讨论】:

以上是关于从 Spring Boot 连接到 Heroku Postgres的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 Spring JPA 从本地 Springboot 项目连接到 heroku-postgres 时出现 UnknownHostException

如何从 Spring Boot 连接到在线 MongoDB 数据库?

从 Spring Boot 应用程序连接到 mysql 容器

从 Spring Boot 连接到 AWS SQS 时出错

无法从 docker 容器内的 Spring Boot 连接到 Redis

无法从 Spring Boot 应用程序连接到 Bigtable