Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate DDL with spring boot using a custom delimiter

I want generate create and drop ddl scripts using spring boot v1.4.3 with JPA - Hibernate 5.0.11.

Most answers I found use the javax.persistence.schema-generation properties. E.g. https://stackoverflow.com/a/36966419/974186

The problem with this approach is the it outputs the sql statements without an delimiter. E.g.

create table ... (...)
create table ... (...)

I want it to output the statements with the delimiter ;

create table ... (...);
create table ... (...);

But I can't find any javax.persistence.schema-generation property to configure it.

So I thought to use the SchemaExport from hibernate, because you can set the delimiter property. But to create a SchemaExport I need a MetadataImplementor (non deprected api).

I can not figure out how to get a MetadataImplementor from spring boot.

Does anyone know if there is either

  • a javax.persistence.schema-generation property to define the delimiter
  • or how to create a SchemaExport (get the dependencies)
  • or has another solution?

Here is some code you can play with

@SpringBootApplication
@ComponentScan(basePackageClasses = Application.class)
@EntityScan(basePackageClasses = User.class)
public class Application {

    @Bean
    public ApplicationRunner getApplicationRunner() {
        return new ApplicationRunner() {

            public void run(ApplicationArguments args) throws Exception {
                // MetadataImplementor metadataImplementor = ???;
                // new SchemaExport(metadataImplementor);
            }
        };
    }

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Application.class);
        application.run(args);
    }

}

Entity Class

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    public Long getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
}

Another entity class

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    public Long getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
}

application.properties

spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=create.sql

maven dependencies

<properties>
    <spring.boot.version>1.4.3.RELEASE</spring.boot.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
</dependencies>   

With Hibernate 5.0

I just tried the code above with hibernate 5.0.11.Final. The only thing you must change is

SchemaExport schemaExport = new SchemaExport((MetadataImplementor) metadata);
schemaExport.setDelimiter(";");
schemaExport.setFormat(false);
schemaExport.setOutputFile(dropAndCreateDdlFile.getAbsolutePath());

schemaExport.execute(true, false, false, false);

or of course let the java configuration return a MetadataImplementor instead of Metadata and change the ApplicationRunner constructor param.

like image 293
René Link Avatar asked Jan 24 '17 15:01

René Link


3 Answers

You might want to try setting the following Hibernate property:

spring.jpa.properties.hibernate.hbm2ddl.delimiter=;

#in addition to the other standard JPA properties you refered to, namely:
spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=create.sql

I found this to do the job in a Spring Boot 2.1.2.RELEASE + matching Hibernate version (5.3.7.Final) project where I needed the same feature.

It might very well work on your not-so-different Hibernate environment.


Slightly off-topic, but one issue I have remains: Hibernate appends to create.sql. I whish I found a way to have it replace the file contents.

like image 143
Alain BECKER Avatar answered Oct 22 '22 13:10

Alain BECKER


Finally after a lot of investigation I think I found an easy solution that uses public APIs. The solution I found uses hibernate 5.2 (more concrete 5.2.6.Final). But I think it can also be adapted to 5.0

Here is my spring java configuration

@Configuration
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class })
public class HibernateJavaConfig {

    @ConditionalOnMissingBean({ Metadata.class })
    @Bean
    public Metadata getMetadata(StandardServiceRegistry standardServiceRegistry,
            PersistenceUnitInfo persistenceUnitInfo) {
        MetadataSources metadataSources = new MetadataSources(standardServiceRegistry);

        List<String> managedClassNames = persistenceUnitInfo.getManagedClassNames();
        for (String managedClassName : managedClassNames) {
            metadataSources.addAnnotatedClassName(managedClassName);
        }

        Metadata metadata = metadataSources.buildMetadata();
        return metadata;
    }

    @ConditionalOnMissingBean({ StandardServiceRegistry.class })
    @Bean
    public StandardServiceRegistry getStandardServiceRegistry(JpaProperties jpaProperties) {
        StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder();

        Map<String, String> properties = jpaProperties.getProperties();
        ssrb.applySettings(properties);

        StandardServiceRegistry ssr = ssrb.build();
        return ssr;
    }

    @ConditionalOnMissingBean({ PersistenceUnitInfo.class })
    @Bean
    public PersistenceUnitInfo getPersistenceUnitInfo(EntityScanPackages entityScanPackages) {
        List<String> packagesToScan = entityScanPackages.getPackageNames();

        DefaultPersistenceUnitManager persistenceUnitManager = new DefaultPersistenceUnitManager();

        String[] packagesToScanArr = (String[]) packagesToScan.toArray(new String[packagesToScan.size()]);
        persistenceUnitManager.setPackagesToScan(packagesToScanArr);
        persistenceUnitManager.afterPropertiesSet();

        PersistenceUnitInfo persistenceUnitInfo = persistenceUnitManager.obtainDefaultPersistenceUnitInfo();
        return persistenceUnitInfo;
    }

}

The java configuration creates a Metadata bean. This bean can be used in hibernate 5.2 to execute a schema generation. E.g.

@Component
public class GenerateDDLApplicationRunner implements ApplicationRunner {

    private Metadata metadata;

    public GenerateDDLApplicationRunner(Metadata metadata) {
        this.metadata = metadata;
    }

    public void run(ApplicationArguments args) throws Exception {
        File dropAndCreateDdlFile = new File("drop-and-create.ddl");
        deleteFileIfExists(dropAndCreateDdlFile);

        SchemaExport schemaExport = new SchemaExport();
        schemaExport.setDelimiter(";");
        schemaExport.setFormat(false);
        schemaExport.setOutputFile(dropAndCreateDdlFile.getAbsolutePath());

        schemaExport.execute(EnumSet.of(TargetType.SCRIPT), Action.BOTH, metadata);
    }

    private void deleteFileIfExists(File dropAndCreateDdlFile) {
        if (dropAndCreateDdlFile.exists()) {
            if (!dropAndCreateDdlFile.isFile()) {
                String msg = MessageFormat.format("File is not a normal file {0}", dropAndCreateDdlFile);
                throw new IllegalStateException(msg);
            }

            if (!dropAndCreateDdlFile.delete()) {
                String msg = MessageFormat.format("Unable to delete file {0}", dropAndCreateDdlFile);
                throw new IllegalStateException(msg);
            }
        }
    }

}

The hibernate dialect is configured using the spring boot application.properties. In my case:

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL57InnoDBDialect
like image 26
René Link Avatar answered Oct 22 '22 14:10

René Link


I have modified the solution from René to work in Spring Boot 2. Tested with version 2.0.4:

@Configuration
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class })
public class HibernateMetadataBean {

    @ConditionalOnMissingBean({ Metadata.class })
    @Bean
    public Metadata getMetadata(StandardServiceRegistry standardServiceRegistry,
                                PersistenceUnitInfo persistenceUnitInfo) {
        MetadataSources metadataSources = new MetadataSources(standardServiceRegistry);

        List<String> managedClassNames = persistenceUnitInfo.getManagedClassNames();
        for (String managedClassName : managedClassNames) {
            metadataSources.addAnnotatedClassName(managedClassName);
        }

        return metadataSources.buildMetadata();
    }

    @ConditionalOnMissingBean({ StandardServiceRegistry.class })
    @Bean
    public StandardServiceRegistry getStandardServiceRegistry(JpaProperties jpaProperties) {
        StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder();

        Map<String, String> properties = jpaProperties.getProperties();
        ssrb.applySettings(properties);

        return ssrb.build();
    }

    @ConditionalOnMissingBean({ PersistenceUnitInfo.class })
    @Bean
    public PersistenceUnitInfo getPersistenceUnitInfo(BeanFactory beanFactory) {
        List<String> packagesToScan = EntityScanPackages.get(beanFactory).getPackageNames();
        if (packagesToScan.isEmpty() && AutoConfigurationPackages.has(beanFactory)) {
            packagesToScan = AutoConfigurationPackages.get(beanFactory);
        }

        DefaultPersistenceUnitManager persistenceUnitManager = new DefaultPersistenceUnitManager();

        String[] packagesToScanArr = StringUtils.toStringArray(packagesToScan);
        persistenceUnitManager.setPackagesToScan(packagesToScanArr);
        persistenceUnitManager.afterPropertiesSet();

        return persistenceUnitManager.obtainDefaultPersistenceUnitInfo();
    }

}
like image 21
Eduardo Franceschi Avatar answered Oct 22 '22 14:10

Eduardo Franceschi