Monday, February 25, 2013

Hibernate schema export with Hibernate Validator constraints accepted and Spring configured persistence

What is the goal:

  • using Spring Framework for configuration of persistence settings (JPA 2.0 with Hibernate provider)
  • using the configuration for Hibernate schema export tool
  • apply Hibernate Validator constraints to generated schema

Spring persistence configuration

// define datasource - hsqldb for testing purposes - it requires hsqldb to be on classpath
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase database = builder.setType(EmbeddedDatabaseType.HSQL).build();

// standard way how to configure persistence in Spring (usually included as a part of some configuration class)
LocalContainerEntityManagerFactoryBean lcemfb = new LocalContainerEntityManagerFactoryBean();
lcemfb.setPersistenceXmlLocation("classpath:example/path/to/persistence.xml");
lcemfb.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
lcemfb.setDataSource(database);
lcemfb.afterPropertiesSet();

Create Hibernate configuration from Spring configured persistence

Ejb3Configuration cfg = new Ejb3Configuration();
// explained later, ignore for now
if (beanValidationInClasspath()) {
    injectBeanValidationConstraintToDdlTranslator(cfg, HSQLDialect.class);
}
// set datasource must be called before configure
cfg.setDataSource(lcemfb.getDataSource());
cfg = cfg.configure(lcemfb.getPersistenceUnitInfo(), lcemfb.getJpaPropertyMap());
Configuration configuration = cfg.getHibernateConfiguration();

Integrate configuration with Hibernate Validator annotations

Hibernate Validator constraints like @Size or @NotNull are applied to database schema if configured so. You can turn this feature on by configuration property of persistence unit:

If using standalone schema export Hibernate Validator integration is not handled for us and we have to provide an explicit Hibernate Validator integration. It is not straightforward as could be, but possible. Tested with Hibernate 4.1.9, Hibernate Validator 4.3.0. Inspiration was taken from: Hibernate Forum
This integration is usually performed by BeanValidationIntegrator. Unfortunately, that integration will only be activated upon initialization of the ServiceRegistry, which initializes DatasourceConnectionProviderImpl, which looks up the datasource, which requires a JNDI context ... We therefore reimplement the relevant parts of BeanValidatorIntegrator. Since that must occur after secondPassCompile(), which is invoked by configuration.generateSchemaCreationScript, which is invoked by SchemaExport, some fancy subclassing is needed to invoke the integration at the right time.
private static void injectBeanValidationConstraintToDdlTranslator(Ejb3Configuration jpaCfg, final Class dialectClass) {
        try {
            Field cfgField = Ejb3Configuration.class.getDeclaredField("cfg");
            cfgField.setAccessible(true);
            cfgField.set(jpaCfg, new Configuration() {
                @Override
                protected void secondPassCompile() throws MappingException {
                    super.secondPassCompile();
                   
                    try {
                        // thank you, hibernate folks, for making this useful class package private ...
                        Method applyDDL = Class.forName("org.hibernate.cfg.beanvalidation.TypeSafeActivator") //
                                .getMethod("applyDDL", Collection.class, Properties.class, Dialect.class);
                        applyDDL.setAccessible(true);
                        applyDDL.invoke(null, classes.values(), getProperties(), dialectClass.newInstance());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                   
                }
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

Finally run schema export

SchemaExport schemaExport = new SchemaExport(configuration)
.setOutputFile("output.sql")
.setDelimiter(";")
.setFormat(true)
.setHaltOnError(true);
schemaExport.create(true, false);

No comments:

Post a Comment