从 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.active
是 h2
以使用 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 数据源上启用某些测试,例如testOnBorrow
:
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
现在,我遇到的两个问题都可能是 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_USERNAME
和JDBC_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 容器