35

In Hibernate 4.x, I used to generate and export the schema as defined in annotated entities as follows (using Spring to find annotated entities on the class path):

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

This no longer works in Hibernate 5.0:

I didn't really find any obvious references to this change in the migration guide apart from:

Quite a few methods have been removed from Configuration

What is the correct way to generate and export a database on an existing JDBC connection with Hibernate 5.0 based on a set of annotated entities? (Pure JPA-based solutions are fine, too)

(note, just removing the call to generateSchemaCreationScript() seems to work, but I would prefer to be sure to get this right)

Bacteria
  • 8,406
  • 10
  • 50
  • 67
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Thanks for your edit, @NeilStockton. If a pure JPA-based solution that is implemented by Hibernate is possible, I'd take that too as an answer. – Lukas Eder Aug 24 '15 at 09:55
  • you mean create a DDL file for the schema required for classes in the persistence.xml ? (because I don't use Hibernate so consequently don't know what those methods are). Isn't that what properties javax.persistence.schema-generation.scripts.action and javax.persistence.schema-generation.scripts.create-target will do? – Neil Stockton Aug 24 '15 at 10:34
  • The question is really "What is the correct way to *generate* and *export* a database with Hibernate 5.0 based on a set of annotated entities? (Pure JPA-based solutions are fine, too)" – Lukas Eder Aug 24 '15 at 11:18

5 Answers5

32

Thanks to the answers by Vlad and Gunnar, I've managed to find my way through the new configuration API to produce the equivalent export logic with the following. Of course, history shows that this API will break again, so make sure to choose the appropriate version:

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 (without warnings):

The above will produce some nasty warnings, which can either be ignored:

Okt 20, 2016 2:57:16 PM org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator initiateService
WARN: HHH000181: No appropriate connection provider encountered, assuming application will be supplying connections
Okt 20, 2016 2:57:16 PM org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator initiateService
WARN: HHH000342: Could not obtain connection to query metadata : The application must supply JDBC connections

... or you work around them by hacking the following ConnectionProvider into the settings (it shouldn't be required in my opinion)

        .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:

As a reminder, here's how this worked in 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);
Community
  • 1
  • 1
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • 2
    Why does hibernate need a connection? In a test setup, it would not be needed when checking the generated schema against a known good version. – gkephorus Mar 18 '16 at 12:41
  • @gkephorus: I'm not sure what you mean. In my case, the schema is written by some DDL statements, and Hibernate is used to generate entity classes (see also the question). Of course, there are other setups, but this question is about this specific use-case. – Lukas Eder Mar 18 '16 at 12:56
  • 1
    I stand corrected, I should have opened a new question. Sorry. (The answer is correct for this question) – gkephorus Mar 24 '16 at 10:16
  • Lukas, Thank you for the solution. But this doesnt seem to work with HIbernate 5.2 , because there is no way I can pass anything to Schemaexport. Did you come across this problem ? – – Freaky Thommi Jul 12 '16 at 14:06
  • @FreakyThommi: So the API broke again? Interesting... :-( No, I don't have a solution for this... – Lukas Eder Jul 12 '16 at 16:22
  • Yes. They have removed the constructor for SchemaExport.Only default constructor is available now – Freaky Thommi Jul 12 '16 at 16:26
  • 1
    @FreakyThommi: OK, I've reverse engineered their latest version. Seems to be working again, now... Answer is updated – Lukas Eder Jul 12 '16 at 16:59
  • If you want to export the database scheme to a local file you should replace `create` or `execute` with `perform` and feed it with a `ScriptTargetOutput` object: similar to: `ScriptTargetOutput targetOutput = new ScriptTargetOutputToFile(new File(destination), StandardCharsets.ISO_8859_1.name()); schemaExport.perform(Action.BOTH, metadata.buildMetadata(), targetOutput);` This thid the trick in Hibernate 5.3 to actually write the scheme to a file – Roman Vottner May 30 '18 at 17:22
  • Is there any way of getting the script in-memory insted of writing it to a file ? – Sergiu Jun 27 '18 at 11:02
  • @Sergiu: Surely there is. Why not ask a new question? – Lukas Eder Jun 27 '18 at 11:04
4

One example of the new SchemaExport initialization is found in 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 );

Of course, you can customize it according to your needs.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • 1
    Good point. That would work of course, but it seems to me that a lot of unnecessary boilerplate types are created. I wonder if all of that is really needed... – Lukas Eder Aug 24 '15 at 08:59
  • 1
    It seems that you can't escape the `MetadataImplementor` object construction, which is indeed bulky. Anyway, it's easy to get lost in so many indirection layers, and this is just for configuration. – Vlad Mihalcea Aug 24 '15 at 09:04
  • 1
    That calls for a Jira issue then. – Vlad Mihalcea Aug 24 '15 at 09:13
3

The new bootstrap API allows for many customizations, but assuming you don't need those, the shortest invocation would look like that, applying default values for service registries and all the settings:

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

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

Update: Providing example for applying configuration propperties

There are several ways to inject properties for connection URL, dialect etc. E.g. you could provide a file hibernate.properties or you use a service registry customized with the required settings:

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

Metadata metadata = new MetadataSources( registry )
    .build();
Gunnar
  • 18,095
  • 1
  • 53
  • 73
  • Assuming I want `Target.BOTH`, I'll need a `connection` and a `hibernate.dialect` configuration (as shown in the question). Where would those go? – Lukas Eder Aug 24 '15 at 12:36
  • Updated the answer to include an example of specifying configuration properties – Gunnar Aug 24 '15 at 13:07
  • I'm sorry if I may appear picky :) But I've updated an important detail in the question - The fact that I have a standalone JDBC `Connection` on which to execute the DDL is important (to me). I don't want Hibernate to manage the connection, in this particular case... – Lukas Eder Aug 24 '15 at 14:58
  • Just invoke the constructor `SchemaExport(MetadataImplementor, Connection)` then. – Gunnar Aug 25 '15 at 06:54
  • Thanks for the update. I really have to specify the dialect as well, [or I get this exception](https://gist.github.com/lukaseder/f0ccc4e788bc25569e4c). I wonder if Hibernate couldn't guess the dialect from the JDBC connection, if it is supplied to `SchemaExport`? I have now documented the specific solution I was looking for [in an additional answer](http://stackoverflow.com/a/32227118/521799) – Lukas Eder Aug 26 '15 at 12:49
  • Output file "my-statements.ddl" is empty. But I expect updates from Hibernate mapping to appear in this file... – naXa stands with Ukraine May 05 '17 at 13:45
1

I exported it this way with 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);
    }
}
Kristof Neirynck
  • 3,934
  • 1
  • 33
  • 47
0

In case one is using JPA 2.1+ - there is a very simple build-in possibility to generate the ddl. just set the following jpa properties and the ddl files will be created. With spring boot one could write a separate main class with those specific config options.

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 with JPA 2.1+

schemagenerator.properties (put in resources folder):

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();
    }
}
fischermatte
  • 3,327
  • 4
  • 42
  • 52
  • Thanks, but in this question, I was explicitly looking for a solution to "What is the correct way to generate and export a database *on an existing JDBC connection*" – Lukas Eder Jun 09 '16 at 15:06