在Hibernate 5中,Configuration.generateSchemaCreationScript()去哪了?

35
在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中,以下内容已不再适用:

我并没有在迁移指南中找到任何明显的关于这种变化的参考,除了:

Configuration中已删除了很多方法

使用Hibernate 5.0基于一组注释实体,在现有的JDBC连接上生成和导出数据库的正确方法是什么?(纯基于JPA的解决方案也可以)

(注意,只需删除对generateSchemaCreationScript()的调用似乎可以正常工作,但我更愿意确保正确性)


感谢您的编辑,@NeilStockton。如果可能的话,我也会接受一个纯基于Hibernate实现的JPA解决方案作为答案。 - Lukas Eder
你的意思是为 persistence.xml 中所需的类创建一个 DDL 文件吗?(因为我不使用 Hibernate,所以不知道那些方法是什么)。这不就是 javax.persistence.schema-generation.scripts.action 和 javax.persistence.schema-generation.scripts.create-target 属性所要做的吗? - Neil Stockton
问题实际上是:“基于一组带注释的实体,使用Hibernate 5.0生成和导出数据库的正确方法是什么?(纯JPA解决方案也可以)” - Lukas Eder
5个回答

32

感谢VladGunnar的回答,我成功地通过新的配置API找到了以下等效导出逻辑。当然,历史表明这个API将再次中断,因此请确保选择适当的版本:

Hibernate 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 (无警告):

上述代码会产生一些令人不快的警告,可以选择忽略:

Okt 20, 2016 2:57:16 PM org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator initiateService
WARN: HHH000181: 没有找到适当的连接提供器,默认情况下应用程序将提供连接
Okt 20, 2016 2:57:16 PM org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator initiateService
WARN: 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;
            }
        })

Hibernate 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);

Hibernate 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);

2
为什么Hibernate需要连接?在测试设置中,当检查生成的模式与已知的良好版本相对比时,它是不需要的。 - gkephorus
@gkephorus:我不确定你的意思。在我的情况下,模式是通过一些DDL语句编写的,并且使用Hibernate生成实体类(请参见问题)。当然,还有其他的设置,但这个问题是关于这个特定的用例。 - Lukas Eder
1
我认错了,我应该开一个新的问题。抱歉。(这个问题的答案是正确的) - gkephorus
是的。他们已经移除了SchemaExport的构造函数。现在只有默认构造函数可用。 - Freaky Thommi
1
@FreakyThommi:好的,我已经反向工程了他们的最新版本。现在似乎又可以正常工作了...回答已更新。 - Lukas Eder
显示剩余5条评论

4

新的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 );

当然,您可以根据自己的需求进行定制。

1
好观点。当然,那样做是可行的,但我认为会创建很多不必要的样板类型。我想知道是否真的需要这么多。 - Lukas Eder
1
似乎你不能避免构造MetadataImplementor对象,这确实有点繁琐。无论如何,在那么多间接层中很容易迷失,而这只是为了配置。 - Vlad Mihalcea
1
那就需要创建一个 Jira 问题了。 - Vlad Mihalcea

3
新的 Bootstrap 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();

假设我想要Target.BOTH,我将需要一个connection和一个hibernate.dialect配置(如问题所示)。那些应该放在哪里? - Lukas Eder
更新了答案,包括指定配置属性的示例。 - Gunnar
如果我看起来有点挑剔,我很抱歉 :) 但是我在问题中更新了一个重要细节 - 我拥有一个独立的JDBC Connection,在其中执行DDL非常重要(对我而言)。在这种特殊情况下,我不想让Hibernate管理连接... - Lukas Eder
只需调用构造函数 SchemaExport(MetadataImplementor, Connection) 即可。 - Gunnar
感谢更新。我真的必须指定方言,否则我会得到这个异常。我想知道如果Hibernate从JDBC连接中获取方言是否可以猜测出来,如果它被提供给SchemaExport?我现在已经在另一个答案中记录了我正在寻找的具体解决方案。 - Lukas Eder
输出文件“my-statements.ddl”为空。但我期望从Hibernate映射中的更新出现在这个文件中... - naXa stands with Ukraine

1

我使用 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);
    }
}

0
如果使用JPA 2.1+,则有一个非常简单的内置功能可以生成ddl。只需设置以下jpa属性即可创建ddl文件。使用Spring Boot,可以编写一个具有特定配置选项的单独的主类。 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

使用JPA2.1+的Spring Boot

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();
    }
}

谢谢,但在这个问题中,我明确寻求的是“在现有的JDBC连接上生成和导出数据库的正确方法”的解决方案。 - Lukas Eder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接