Куда делась Configuration.generateSchemaCreationScript() в Hibernate 5

В Hibernate 4.x я использовал для создания и экспорта схемы, определенной в аннотированных сущностях, следующим образом (используя Spring для поиска аннотированных сущностей на пути к классу):

Connection connection = 
    DriverManager.getConnection("jdbc:h2:mem:jooq-meta-extensions", "sa", "");

Configuration configuration = new Configuration()
    .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");

// [...] adding annotated classes to Configuration here...

configuration.generateSchemaCreationScript(
    Dialect.getDialect(configuration.getProperties()));
SchemaExport export = new SchemaExport(configuration, connection);
export.create(true, true);

Это больше не работает в Hibernate 5.0:

Я не нашел явных упоминаний об этом изменении в руководстве по миграции. Кроме:

Довольно много методов было удалено из конфигурации

Каков правильный способ создания и экспорта базы данных по существующему соединению JDBC с помощью Hibernate 5.0 на основе набора аннотированных сущностей? (чистые решения на основе JPA тоже подходят)

(обратите внимание, просто удаление вызова generateSchemaCreationScript() похоже работает, но я бы предпочел убедиться, что все сделано правильно)


person Lukas Eder    schedule 24.08.2015    source источник
comment
Спасибо за ваше редактирование, @NeilStockton. Если возможно чистое решение на основе JPA, реализованное Hibernate, я бы тоже воспринял это как ответ.   -  person Lukas Eder    schedule 24.08.2015
comment
Вы имеете в виду создать файл DDL для схемы, необходимой для классов в файле persistence.xml? (потому что я не использую Hibernate, поэтому не знаю, что это за методы). Разве это не то, что будут делать свойства javax.persistence.schema-generation.scripts.action и javax.persistence.schema-generation.scripts.create-target?   -  person Neil Stockton    schedule 24.08.2015
comment
На самом деле вопрос заключается в том, как правильно генерировать и экспортировать базу данных с помощью Hibernate 5.0 на основе набора аннотированных сущностей? (чистые решения на основе JPA тоже подходят)   -  person Lukas Eder    schedule 24.08.2015


Ответы (5)


Благодаря ответам Влада и Гуннар, мне удалось найти способ с помощью нового API конфигурации создать эквивалентную логику экспорта со следующим. Конечно, история показывает, что этот API снова сломается, поэтому обязательно выберите подходящую версию:

Спящий режим 5.2:

MetadataSources metadata = new MetadataSources(
    new StandardServiceRegistryBuilder()
        .applySetting("hibernate.dialect", "org.hibernate.dialect.H2Dialect")
        .applySetting("javax.persistence.schema-generation-connection", connection)
        .build());

// [...] adding annotated classes to metadata here...
metadata.addAnnotatedClass(...);

SchemaExport export = new SchemaExport();
export.create(EnumSet.of(TargetType.DATABASE), metadata.buildMetadata());

Hibernate 5.2 (без предупреждений):

Вышеприведенное приведет к некоторым неприятным предупреждениям, которые можно игнорировать:

20 октября 2016 г. 14:57:16 org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator InitialService
ПРЕДУПРЕЖДЕНИЕ: HHH000181: не найден подходящий провайдер подключения, предполагается, что приложение будет предоставлять подключения
20 октября , 2016 14:57:16 org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator InitialService
ПРЕДУПРЕЖДЕНИЕ: HHH000342: не удалось получить соединение с метаданными запроса: приложение должно предоставлять соединения JDBC

... или вы обходите их, взломав следующие ConnectionProvider в настройках (на мой взгляд, это не должно требоваться)

        .applySetting(AvailableSettings.CONNECTION_PROVIDER, new ConnectionProvider() {
            @Override
            public boolean isUnwrappableAs(Class unwrapType) {
                return false;
            }
            @Override
            public <T> T unwrap(Class<T> unwrapType) {
                return null;
            }
            @Override
            public Connection getConnection() {
                return connection; // Interesting part here
            }
            @Override
            public void closeConnection(Connection conn) throws SQLException {}

            @Override
            public boolean supportsAggressiveRelease() {
                return true;
            }
        })

Гибернация 5.0:

MetadataSources metadata = new MetadataSources(
    new StandardServiceRegistryBuilder()
        .applySetting("hibernate.dialect", "org.hibernate.dialect.H2Dialect")
        .build());

// [...] adding annotated classes to metadata here...
metadata.addAnnotatedClass(...);

SchemaExport export = new SchemaExport(
    (MetadataImplementor) metadata.buildMetadata(),
    connection // pre-configured Connection here
);
export.create(true, true);

Спящий режим 4:

Напоминаю, вот как это работало в Hibernate 4:

Configuration configuration = new Configuration()
    .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");

// [...] adding annotated classes to metadata here...
configuration.addAnnotatedClass(...);

configuration.generateSchemaCreationScript(
    Dialect.getDialect(configuration.getProperties()));
SchemaExport export = new SchemaExport(configuration, connection);
export.create(true, true);
person Lukas Eder    schedule 26.08.2015
comment
Зачем спящему режиму нужно соединение? В тестовой настройке это не понадобится при проверке сгенерированной схемы на заведомо исправную версию. - person gkephorus; 18.03.2016
comment
@gkephorus: я не понимаю, что вы имеете в виду. В моем случае схема написана некоторыми операторами DDL, а Hibernate используется для генерации классов сущностей (см. также вопрос). Конечно, есть и другие настройки, но этот вопрос касается этого конкретного варианта использования. - person Lukas Eder; 18.03.2016
comment
Я исправлен, я должен был открыть новый вопрос. Извиняюсь. (Ответ правильный на этот вопрос) - person gkephorus; 24.03.2016
comment
Лукас, спасибо за решение. Но это, похоже, не работает с Hibernate 5.2, потому что я никак не могу передать что-либо в Schemaexport. Вы сталкивались с этой проблемой? – - person Freaky Thommi; 12.07.2016
comment
@FreakyThommi: Итак, API снова сломался? Интересно... :-( Нет, у меня нет решения для этого... - person Lukas Eder; 12.07.2016
comment
Да. Они удалили конструктор для SchemaExport. Теперь доступен только конструктор по умолчанию. - person Freaky Thommi; 12.07.2016
comment
@FreakyThommi: Хорошо, я реконструировал их последнюю версию. Кажется, теперь снова работает... Ответ обновлен - person Lukas Eder; 12.07.2016
comment
Если вы хотите экспортировать схему базы данных в локальный файл, вы должны заменить create или execute на perform и передать его с помощью объекта ScriptTargetOutput: аналогично: ScriptTargetOutput targetOutput = new ScriptTargetOutputToFile(new File(destination), StandardCharsets.ISO_8859_1.name()); schemaExport.perform(Action.BOTH, metadata.buildMetadata(), targetOutput); Это трюк в Hibernate 5.3, чтобы фактически записать схему в файл. - person Roman Vottner; 30.05.2018
comment
Есть ли способ получить скрипт в памяти вместо записи его в файл? - person Sergiu; 27.06.2018
comment
@Sergeu: Конечно, есть. Почему бы не задать новый вопрос? - person Lukas Eder; 27.06.2018

Один из примеров новой инициализации SchemaExport можно найти в SchemaExportTask:

final BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder().build();

final MetadataSources metadataSources = new MetadataSources( bsr );
final StandardServiceRegistryBuilder ssrBuilder = new StandardServiceRegistryBuilder( bsr );

if ( configurationFile != null ) {
    ssrBuilder.configure( configurationFile );
}
if ( propertiesFile != null ) {
    ssrBuilder.loadProperties( propertiesFile );
}
ssrBuilder.applySettings( getProject().getProperties() );

for ( String fileName : getFiles() ) {
    if ( fileName.endsWith(".jar") ) {
        metadataSources.addJar( new File( fileName ) );
    }
    else {
        metadataSources.addFile( fileName );
    }
}


final StandardServiceRegistryImpl ssr = (StandardServiceRegistryImpl) ssrBuilder.build();
final MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder( ssr );

ClassLoaderService classLoaderService = bsr.getService( ClassLoaderService.class );
if ( implicitNamingStrategy != null ) {
    metadataBuilder.applyImplicitNamingStrategy(
            (ImplicitNamingStrategy) classLoaderService.classForName( implicitNamingStrategy ).newInstance()
    );
}
if ( physicalNamingStrategy != null ) {
    metadataBuilder.applyPhysicalNamingStrategy(
            (PhysicalNamingStrategy) classLoaderService.classForName( physicalNamingStrategy ).newInstance()
    );
}

return new SchemaExport( (MetadataImplementor) metadataBuilder.build() )
    .setHaltOnError( haltOnError )
    .setOutputFile( outputFile.getPath() )
    .setDelimiter( delimiter );

Конечно, вы можете настроить его в соответствии с вашими потребностями.

person Vlad Mihalcea    schedule 24.08.2015
comment
Хорошая точка зрения. Это, конечно, сработало бы, но мне кажется, что создается много ненужных шаблонных типов. Интересно, нужно ли все это на самом деле... - person Lukas Eder; 24.08.2015
comment
Кажется, что вы не можете избежать конструкции объекта MetadataImplementor, которая действительно громоздка. В любом случае, легко заблудиться в таком количестве косвенных слоев, и это только для настройки. - person Vlad Mihalcea; 24.08.2015
comment
Тогда это требует проблемы с Jira. - person Vlad Mihalcea; 24.08.2015

Новый API начальной загрузки допускает множество настроек, но если они вам не нужны, самый короткий вызов будет выглядеть так, применяя значения по умолчанию для реестров служб и всех настроек:

Metadata metadata = new MetadataSources()
    .addAnnotatedClass( MyEntity.class )
    .build();

new SchemaExport( (MetadataImplementor) metadata )
    .setOutputFile( "my-statements.ddl" )
    .create( Target.NONE );

Обновление: пример применения свойств конфигурации.

Существует несколько способов ввода свойств для URL-адреса подключения, диалекта и т. д. Например. вы можете предоставить файл hibernate.properties или использовать реестр службы, настроенный с необходимыми параметрами:

StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
    .applySetting( "hibernate.connection.url", "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1" )
    .build();

Metadata metadata = new MetadataSources( registry )
    .build();
person Gunnar    schedule 24.08.2015
comment
Предполагая, что я хочу Target.BOTH, мне понадобится конфигурация connection и hibernate.dialect (как показано в вопросе). Куда бы они пошли? - person Lukas Eder; 24.08.2015
comment
Обновлен ответ, чтобы включить пример указания свойств конфигурации. - person Gunnar; 24.08.2015
comment
Извините, если я могу показаться придирчивым :) Но я обновил важную деталь в вопросе: тот факт, что у меня есть автономный JDBC Connection, на котором выполняется DDL, важен (для меня). Я не хочу, чтобы Hibernate управлял соединением, в данном конкретном случае... - person Lukas Eder; 24.08.2015
comment
Просто вызовите конструктор SchemaExport(MetadataImplementor, Connection). - person Gunnar; 25.08.2015
comment
Спасибо за обновление. Мне действительно нужно указать диалект, , иначе я получу это исключение. Интересно, не мог ли Hibernate угадать диалект из JDBC-соединения, если он предоставлен SchemaExport? Теперь я задокументировал конкретное решение, которое искал в дополнительном ответе - person Lukas Eder; 26.08.2015
comment
Выходной файл my-statements.ddl пуст. Но я ожидаю, что в этом файле появятся обновления из сопоставления Hibernate... - person naXa; 05.05.2017

Я экспортировал его таким образом с помощью hibernate 5.4.9.Final:

import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;

import java.util.EnumSet;

public class ExportSchema {
    public static void main(String[] args) {
        final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
                .applySetting("hibernate.dialect", "org.hibernate.dialect.H2Dialect")
                .build();
        final Metadata metadata = new MetadataSources(serviceRegistry)
                .addAnnotatedClass(...)
                .buildMetadata();
        new SchemaExport()
                .setFormat(true)
                .setDelimiter(";")
                .setOutputFile("schema.sql")
                .execute(EnumSet.of(TargetType.SCRIPT), SchemaExport.Action.CREATE, metadata);
    }
}
person Kristof Neirynck    schedule 25.12.2019

Если вы используете JPA 2.1+, существует очень простая встроенная возможность создания ddl. просто установите следующие свойства jpa, и файлы ddl будут созданы. С весенней загрузкой можно написать отдельный основной класс с этими конкретными параметрами конфигурации.

JPA 2.1+

javax.persistence.schema-generation.scripts.action=drop-and-create
javax.persistence.schema-generation.scripts.create-target=create.ddl
javax.persistence.schema-generation.scripts.drop-target=drop.ddl

Spring Boot с JPA 2.1+

schemagenerator.properties (поместить в папку ресурсов):

spring.jpa.properties.javax.persistence.schema-generation.scripts.action=drop-and-create
spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=create.ddl
spring.jpa.properties.javax.persistence.schema-generation.scripts.drop-target=drop.ddl
flyway.enabled=false // in case you use flyway for db maintenance

Spring Boot SchemaGenerator:

public class SchemaGenerator {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, new String[]{"--spring.config.name=schemagenerator"}).close();
    }
}
person fischermatte    schedule 09.06.2016
comment
Спасибо, но в этом вопросе я явно искал решение вопроса Как правильно создать и экспортировать базу данных в существующем соединении JDBC - person Lukas Eder; 09.06.2016