Programmatic SchemaExport / SchemaUpdate with Hibernate 5 and Spring 4 Programmatic SchemaExport / SchemaUpdate with Hibernate 5 and Spring 4 spring spring

Programmatic SchemaExport / SchemaUpdate with Hibernate 5 and Spring 4


Basic idea for this problem is:

implementation of org.hibernate.integrator.spi.Integrator which stores required data to some holder. Register implementation as a service and use it where you need.

Work example you can find here https://github.com/valery-barysok/spring4-hibernate5-stackoverflow-34612019


create org.hibernate.integrator.api.integrator.Integrator class

import hello.HibernateInfoHolder;import org.hibernate.boot.Metadata;import org.hibernate.engine.spi.SessionFactoryImplementor;import org.hibernate.service.spi.SessionFactoryServiceRegistry;public class Integrator implements org.hibernate.integrator.spi.Integrator {    @Override    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {        HibernateInfoHolder.setMetadata(metadata);        HibernateInfoHolder.setSessionFactory(sessionFactory);        HibernateInfoHolder.setServiceRegistry(serviceRegistry);    }    @Override    public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {    }}

create META-INF/services/org.hibernate.integrator.spi.Integrator file

org.hibernate.integrator.api.integrator.Integrator

import org.hibernate.boot.spi.MetadataImplementor;import org.hibernate.tool.hbm2ddl.SchemaExport;import org.hibernate.tool.hbm2ddl.SchemaUpdate;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class Application implements CommandLineRunner {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }    @Override    public void run(String... args) throws Exception {        new SchemaExport((MetadataImplementor) HibernateInfoHolder.getMetadata()).create(true, true);        new SchemaUpdate(HibernateInfoHolder.getServiceRegistry(), (MetadataImplementor) HibernateInfoHolder.getMetadata()).execute(true, true);    }}


I would like to add up on Aviad's answer to make it complete as per OP's request.

The internals:

In order to get an instance of MetadataImplementor, the workaround is to register an instance of SessionFactoryBuilderFactory through Java's ServiceLoader facility. This registered service's getSessionFactoryBuilder method is then invoked by MetadataImplementor with an instance of itself, when hibernate is bootstrapped. The code references are below:

  1. Service Loading
  2. Invocation of getSessionFactoryBuilder

So, ultimately to get an instance of MetadataImplementor, you have to implement SessionFactoryBuilderFactory and register so ServiceLoader can recognize this service:

An implementation of SessionFactoryBuilderFactory:

public class MetadataProvider implements SessionFactoryBuilderFactory {    private static MetadataImplementor metadata;    @Override    public SessionFactoryBuilder getSessionFactoryBuilder(MetadataImplementor metadata, SessionFactoryBuilderImplementor defaultBuilder) {        this.metadata = metadata;        return defaultBuilder; //Just return the one provided in the argument itself. All we care about is the metadata :)    }    public static MetadataImplementor getMetadata() {        return metadata;    }}

In order to register the above, create simple text file in the following path(assuming it's a maven project, ultimately we need the 'META-INF' folder to be available in the classpath):

src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory

And the content of the text file should be a single line(can even be multiple lines if you need to register multiple instances) stating the fully qualified class path of your implementation of SessionFactoryBuilderFactory. For example, for the above class, if your package name is 'com.yourcompany.prj', the following should be the content of the file.

com.yourcompany.prj.MetadataProvider

And that's it, if you run your application, spring app or standalone hibernate, you will have an instance of MetadataImplementor available through a static method once hibernate is bootstraped.

Update 1:

There is no way it can be injected via Spring. I digged into Hibernate's source code and the metadata object is not stored anywhere in SessionFactory(which is what we get from Spring). So, it's not possible to inject it. But there are two options if you want it in Spring's way:

  1. Extend existing classes and customize all the way from

LocalSessionFactoryBean -> MetadataSources -> MetadataBuilder

LocalSessionFactoryBean is what you configure in Spring and it has an object of MetadataSources. MetadataSources creates MetadataBuilder which in turn creates MetadataImplementor. All the above operations don't store anything, they just create object on the fly and return. If you want to have an instance of MetaData, you should extend and modify the above classes so that they store a local copy of respective objects before they return. That way you can have a reference to MetadataImplementor. But I wouldn't really recommend this unless it's really needed, because the APIs might change over time.

  1. On the other hand, if you don't mind building a MetaDataImplemetor from SessionFactory, the following code will help you:

    EntityManagerFactoryImpl emf=(EntityManagerFactoryImpl)lcemfb.getNativeEntityManagerFactory();SessionFactoryImpl sf=emf.getSessionFactory();StandardServiceRegistry serviceRegistry = sf.getSessionFactoryOptions().getServiceRegistry();MetadataSources metadataSources = new MetadataSources(new BootstrapServiceRegistryBuilder().build());Metadata metadata = metadataSources.buildMetadata(serviceRegistry);SchemaUpdate update=new SchemaUpdate(serviceRegistry,metadata); //To create SchemaUpdate// You can either create SchemaExport from the above details, or you can get the existing one as follows:try {    Field field = SessionFactoryImpl.class.getDeclaredField("schemaExport");    field.setAccessible(true);    SchemaExport schemaExport = (SchemaExport) field.get(serviceRegistry);} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {    e.printStackTrace();}


Take a look on this one:

public class EntityMetaData implements SessionFactoryBuilderFactory {    private static final ThreadLocal<MetadataImplementor> meta = new ThreadLocal<>();    @Override    public SessionFactoryBuilder getSessionFactoryBuilder(MetadataImplementor metadata, SessionFactoryBuilderImplementor defaultBuilder) {        meta.set(metadata);        return defaultBuilder;    }    public static MetadataImplementor getMeta() {        return meta.get();    }}

Take a look on This Thread which seems to answer your needs