Подключение к Heroku Postgres из Spring Boot
Я ищу самый простой и чистый способ подключения к Heroku Postgres в приложении Spring Boot с использованием JPA/Hibernate.
Я не вижу хорошего, полного примера для этого комбо в документации Heroku или Spring Boot, поэтому я хотел бы документировать это в переполнении стека.
Я пытаюсь пойти с чем-то вроде этого:
@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;
}
}
Я использую Профили, которые кажутся подходящими для того, что я хочу: на 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
DataSource
от Tomcat показался хорошим вариантом, так как в него включены зависимости по умолчанию и потому что в справочном руководстве по Spring Boot написано:
Мы предпочитаем источник данных пула Tomcat за его производительность и параллелизм, поэтому, если он доступен, мы всегда выбираем его.
(Я тоже вижу BasicDataSource
от Commons DBCP используется с Spring Boot. Но для меня это не кажется самым чистым выбором, так как зависимости по умолчанию не включают Commons DBCP. И вообще мне интересно, может ли Apache Commons действительно, в 2015 году, быть рекомендуемым способом подключения к Postgres... Также документация Heroku предлагает " 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>
Текущее состояние: ошибка "Не загружается драйвер 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
В логах вижу что мой postgresDataSource
вызывается просто отлично, и что PostgreSQLDialect используется (без этого он терпел неудачу с "Доступ к DialectResolutionInfo не может быть нулевым, когда" hibernate.dialect "не установлен").
Мои конкретные вопросы
- Ну, как заставить это работать? Я устанавливаю
spring.datasource.driverClassName
, так почему же "Не загружать драйвер JDBC, поскольку свойство driverClassName имеет значение null"? - Является ли использование Tomcat's
DataSource
хорошо или вы бы порекомендовали что-то еще? - Обязательно ли определять
postgresql
зависимость как указано выше с конкретной версией? (Я получаю сообщение об ошибке "не найден подходящий драйвер" без этого.) - Есть ли более простой способ сделать все это (придерживаясь Java-кода и / или свойств; не XML, пожалуйста)?
6 ответов
Чтобы соединение с базой данных работало (стабильно), в настройке, которую я описал в вопросе, отсутствовали две вещи:
- Как указал jny, мне нужно было явно установить драйвер JDBC:
dataSource.setDriverClassName("org.postgresql.Driver");
- (Причина в том, что я определяю собственный источник данных, переопределяя настройки Spring по умолчанию, вызывая мой
spring.datasource.driverClassName
свойство не иметь никакого эффекта. И, насколько я понимаю, из-за динамического характера HerokuDATABASE_URL
Мне нужен собственный источник данных, чтобы он работал.)
- После этого соединение работало, но оно не было стабильным; Я начал получать
org.postgresql.util.PSQLException: This connection has been closed.
после того, как приложение было запущено некоторое время. Несколько неожиданное решение (основанное на этом ответе) состояло в том, чтобы включить определенные тесты, такие какtestOnBorrow
на источнике данных Tomcat: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
Теперь обе проблемы, которые у меня были, могут быть связаны с источником данных от Tomcat (org.apache.tomcat.jdbc.pool
). Очевидно, BasicDataSource (Commons DBCP) имеет более разумные значения по умолчанию. Но, как уже упоминалось в этом вопросе, я скорее использовал что-то, что поставляется с Spring Boot по умолчанию, тем более что это настоятельно рекомендуется в справочном руководстве.
Я открыт для конкурирующих / более простых / лучших решений, поэтому не стесняйтесь писать, особенно если вы можете ответить на вопросы 2–4 в конце вопроса!
С помощью JDBC_DATABASE_*
переменные вместо
Обновление: обратите внимание, что с помощью JDBC_DATABASE_*
гораздо проще, чем выше, как указано в этом ответе. Долгое время у меня было впечатление, что DATABASE_URL
должно быть предпочтительным, но в настоящее время я не так уверен больше.
Самый простой и понятный способ для Spring Boot 2.x с Heroku & Postgres
Я прочитал все ответы, но не нашел то, что Jonik:
Я ищу самый простой и чистый способ подключения к Heroku Postgres в приложении Spring Boot с использованием JPA/Hibernate
Процесс разработки, который большинство людей хотят использовать с Spring Boot & Heroku, включает локальную базу данных H2 в памяти для тестирования и быстрых циклов разработки, а также базу данных Heroku Postgres для подготовки и производства на Heroku.
- Во-первых, для этого вам не нужно использовать профили Spring!
- Второе: вам не нужно писать / изменять код!
Давайте посмотрим, что мы должны делать шаг за шагом. У меня есть пример проекта, который обеспечивает полностью работающее развертывание и настройку Heroku для Postgres - только для полноты, если вы хотите протестировать его самостоятельно: https://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://stackru.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/ (я предполагаю, что база данных Postgres уже настроена для вашего приложения Heroku, что является поведением по умолчанию).
Теперь нажмите на соответствующее приложение Datastore
и переключиться на Settings
Вкладка. Затем нажмите на View Credentials...
, который должен выглядеть примерно так:
Теперь откройте новую вкладку браузера и перейдите в приложение Heroku. Settings
вкладка также. Нажмите на Reveal Config Vars
и создайте следующие переменные среды:
SPRING_DATASOURCE_URL
= jdbc: postgres ql: YourPostgresHerokuHostNameHere: 5432 / YourPostgresHerokuDatabaseNameHere (помните, ведущийjdbc:
иql
дополнение кpostgres
!)SPRING_DATASOURCE_USERNAME
= YourPostgresHerokuUserNameHereSPRING_DATASOURCE_PASSWORD
= YourPostgresHerokuPasswordHereSPRING_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, что действительно здорово - так как вам не нужно мешать сCREATE
Операторы SQL или файлы DDL)
В Heroku это должно выглядеть так:
Теперь это все, что вам нужно сделать! Ваше приложение Heroku перезапускается каждый раз, когда вы изменяете переменную конфигурации - поэтому ваше приложение теперь должно запускать H2 локально и должно быть готово подключено к PostgreSQL при развертывании в Heroku.
Просто если вы спрашиваете: почему мы настраиваем Tomcat JDBC вместо Hikari
Как вы могли заметить, мы добавили tomcat-jdbc
зависимость от нашего pom.xml и настроена SPRING_DATASOURCE_TYPE=org.apache.tomcat.jdbc.pool.DataSource
в качестве переменной среды. Об этом высказывании в документах есть только небольшой намек
Вы можете полностью обойти этот алгоритм и указать используемый пул соединений, установив свойство spring.datasource.type. Это особенно важно, если вы запускаете приложение в контейнере Tomcat, ...
Есть несколько причин, по которым я вернулся к пулу данных Tomcat, а не к стандартному HikariCP Spring Boot 2.x. Как я уже объяснил здесь, если вы не укажете spring.datasource.url
Spring попытается автоматически подключить встроенную базу данных im-memory H2 вместо нашей PostgreSQL. И проблема с Hikari в том, что он поддерживает только spring.datasource.jdbc-url
,
Во-вторых, если я попытаюсь использовать конфигурацию Heroku, как показано для Hikari (так что опуская 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 & Postgres с HikariCP, а с Tomcat JDBC - и я также не хочу тормозить процесс разработки, содержащий локальную базу данных H2, описанную заранее. Помните: мы искали самый простой и чистый способ подключения к Heroku Postgres в приложении Spring Boot с использованием JPA/Hibernate!
Простейшая конфигурация Spring Boot / Heroku / Hibernate
Помимо DATABASE_URL
, который всегда есть, Heroku создает 3 переменные среды во время выполнения. Они есть:
JDBC_DATABASE_URL
JDBC_DATABASE_USERNAME
JDBC_DATABASE_PASSWORD
Как вы знаете, 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
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')
}
Попробуйте использовать JDBC_DATABASE_URL в качестве spring.datasource.url
вместо разбора DATABASE_URL.
Рекомендуется анализ DATABASE_URL, но если вы не можете заставить его работать, JDBC_DATABASE_URL должно быть в порядке.
@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();
}
}
Это лучший ответ для поиска проблем Postgres с примером приложения Java, которое предлагает Heroku.
Вот шаги, которые я сделал, чтобы заставить его работать (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
Это то, что я должен был заставить работать локальную копию моего приложения. Если кто-нибудь нашел лучший способ, пожалуйста, поделитесь!
Я создал библиотеку, чтобы упростить эту задачу: https://github.co m/vic-cw/heroku-postgres-helper
Это тем более полезно, если вам нужен доступ к базе данных как в сценарии сборки, так и в логике приложения. Посмотрите, почему здесь.
Применение:
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();