Подключение к 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 "не установлен").

Мои конкретные вопросы

  1. Ну, как заставить это работать? Я устанавливаю spring.datasource.driverClassName, так почему же "Не загружать драйвер JDBC, поскольку свойство driverClassName имеет значение null"?
  2. Является ли использование Tomcat's DataSource хорошо или вы бы порекомендовали что-то еще?
  3. Обязательно ли определять postgresql зависимость как указано выше с конкретной версией? (Я получаю сообщение об ошибке "не найден подходящий драйвер" без этого.)
  4. Есть ли более простой способ сделать все это (придерживаясь Java-кода и / или свойств; не XML, пожалуйста)?

6 ответов

Решение

Чтобы соединение с базой данных работало (стабильно), в настройке, которую я описал в вопросе, отсутствовали две вещи:

  • Как указал 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.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-Datastore-учетные данные-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, что действительно здорово - так как вам не нужно мешать с CREATE Операторы SQL или файлы DDL)

В Heroku это должно выглядеть так:

Heroku-весна-загрузка-приложение-Postgres-конфигурация

Теперь это все, что вам нужно сделать! Ваше приложение 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();
Другие вопросы по тегам