10

This question is specifically about programmatically creating a JPA EntityManagerFactory backed by Hibernate 5, meaning without configuration xml files and without using Spring. Also, this question is specifically about creating an EntityManagerFactory with a Hibernate Interceptor.

I know how to create a Hibernate SessionFactory the way I want, but I do not want a Hibernate SessionFactory, I want a JPA EntityManagerFactory backed by a Hibernate SessionFactory. Given an EntityManagerFactory there is a way to obtain the underlying SessionFactory, but if what you have is a SessionFactory and all you want is an EntityManagerFactory wrapper around it, it appears that you are out of luck.

With Hibernate version 4.2.2 Ejb3Configuration was already deprecated, but there seemed to be no other way to programmatically create an EntityManagerFactory, so I was doing something like this:

@SuppressWarnings( "deprecation" )
EntityManagerFactory buildEntityManagerFactory(
        UnmodifiableMap<String,String> properties,
        UnmodifiableCollection<Class<?>> annotatedClasses, 
        Interceptor interceptor )
{
    Ejb3Configuration cfg = new Ejb3Configuration();
    for( Binding<String,String> binding : properties )
        cfg.setProperty( binding.key, binding.value );
    for( Class<?> annotatedClass : annotatedClasses )
        cfg.addAnnotatedClass( annotatedClass );
    cfg.setInterceptor( interceptor );
    return cfg.buildEntityManagerFactory();
}

With Hibernate 4.3.0 Ejb3Configuration was removed, so I had to make use of this hack:

EntityManagerFactory buildEntityManagerFactory(
        UnmodifiableMap<String,String> properties,
        UnmodifiableCollection<Class<?>> annotatedClasses,
        Interceptor interceptor )
{
    Configuration cfg = new Configuration();
    for( Binding<String,String> binding : properties )
        cfg.setProperty( binding.key, binding.value );
    for( Class<?> annotatedClass : annotatedClasses )
        cfg.addAnnotatedClass( annotatedClass );
    cfg.setInterceptor( interceptor );
    StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder();
    ssrb.applySettings( cfg.getProperties() ); //??? why again?
    ServiceRegistry serviceRegistry = ssrb.build();
    return new EntityManagerFactoryImpl( PersistenceUnitTransactionType.RESOURCE_LOCAL, /**/
            /*discardOnClose=*/true, /*sessionInterceptorClass=*/null, /**/
            cfg, serviceRegistry, null );
}

(It is a hack because I am instantiating EntityManagerFactoryImpl which is in package org.hibernate.jpa.internal.)

Now, with Hibernate 5 they have changed the constructor of EntityManagerFactoryImpl, so the above code does not work. I can waste a few hours trying to figure out how to set things up so that I can invoke that constructor, but I am sure that after a couple of Hibernate versions, that won't work anymore, either.

So, this is my question:

Does anybody know of a nice and clean way of implementing this function

EntityManagerFactory buildEntityManagerFactory( 
        UnmodifiableMap<String,String> properties,
        UnmodifiableCollection<Class<?>> annotatedClasses, 
        Interceptor interceptor )

so as to create a Hibernate EntityManagerFactory programmatically, meaning without configuration xml files and without using Spring but with a Hibernate Interceptor ?

There is this old question: Hibernate create JPA EntityManagerFactory with out persistence.xml but it has an answer for an older version of Hibernate, which has already been anticipated in this question. That won't do, because I want it to work with Hibernate 5, and ideally, in a way which does not use anything deprecated or internal, so as to have some chances of working for a long time to come.

Community
  • 1
  • 1
Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • Do you really need the interceptor (they make it hard to register for a reason). What is so special about it that couldn't be replaced with a JPA EventListener and would work without hacks, workarounds etc. – M. Deinum Sep 21 '15 at 13:22
  • @M.Deinum JPA wants to instantiate my entity listeners by itself, requiring them to have parameterless constructors. That's unacceptable, to put it mildly. Hibernate allows me to supply the actual instance of my interceptor, so I can construct it any way I want. Also, JPA entity listeners do not allow me to instantiate my own entities. Again, the Hibernate Interceptor gives me the freedom to instantiate my own entities. That's of paramount importance. For more information, see http://stackoverflow.com/a/29433238/773113 – Mike Nakis Sep 21 '15 at 13:32
  • @MikeNakis Have you tried to contact dev group? Have you resolved that situation? I have very similar issue as yours. – message Jan 08 '16 at 13:00
  • @message No, I haven't. And it is not a priority of mine any more to correct this problem. But if you find something, please do tell. I would be interested to know, for future reference. – Mike Nakis Jan 08 '16 at 14:25

2 Answers2

0

The easiest way is to pass along a org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor reference, which is an abstraction over "persistence unit" information. In normal JPA bootstrapping Hibernate would build a PersistenceUnitDescriptor over the persistence.xml (for what JPA calls "SE bootstrapping") or over the javax.persistence.spi.PersistenceUnitInfo (for what JPA calls "EE bootstrapping).

But it is an abstraction for a reason :) You could create your own and pass what you want Hibernate to use. The intended way this works is starting from org.hibernate.jpa.boot.spi.Bootstrap, e.g.:

EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder(
        new CustomPersistenceUnitDescriptor(),
        Collections.emptyMap()
).build();

...

class CustomPersistenceUnitDescriptor implements PersistenceUnitDescriptor {
    @Override
    public Properties getProperties() {
        final Properties properties = new Properties();
        properties.put( AvailableSettngs.INTERCEPTOR, new MyInterceptor( ... );
        return properties;
    }

    ...
}
Steve Ebersole
  • 9,339
  • 2
  • 48
  • 46
  • Thanks! It will be some time before I get the chance to check this and see if it works for me, so hang in there. – Mike Nakis Aug 31 '16 at 11:27
  • @MikeNakis I had the same kind of issues (without the interceptor) that I resolved via code similar to the answer in http://stackoverflow.com/a/42372648/48136. Note that it's possible to avoid using `HibernatePersistenceProvider` directly by re-using some code in the `javax.persistence.Persistence` to find the provider. – bric3 Feb 21 '17 at 16:29
0

After a lot of research i found this solution working:

Create a PersistenceProvider which injects an Interceptor:

import org.hibernate.Interceptor;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
import org.springframework.beans.factory.annotation.Autowired;

import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceUnitInfo;
import java.util.Map;

public class InterceptorAwareHibernatePersistenceProvider extends HibernatePersistenceProvider {

    @Autowired
    private Interceptor interceptor;

    /**
     * 2017-05-24 · reworked from SpringHibernateJpaPersistenceProvider so that we can inject a custom
     * {@link EntityManagerFactoryBuilderImpl}; previous implementation that overrides
     * {@link InterceptorAwareHibernatePersistenceProvider#getEntityManagerFactoryBuilder} no longer works
     * as there are several paths with various arguments and the overloaded one was no longer called.
     */
    @Override
    @SuppressWarnings("rawtypes")
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
        return new EntityManagerFactoryBuilderImpl(new PersistenceUnitInfoDescriptor(info), properties) {
            @Override
            protected void populate(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) {
                super.populate(sfBuilder, ssr);

                if (InterceptorAwareHibernatePersistenceProvider.this.interceptor == null) {
                    throw new IllegalStateException("Interceptor must not be null");
                } else {
                    sfBuilder.applyInterceptor(InterceptorAwareHibernatePersistenceProvider.this.interceptor);
                }
            }
        }.build();
    }
}

Create the Interceptor:

public class TableNameInterceptor extends EmptyInterceptor {

    @Override
    public String onPrepareStatement(String sql) {
        String mandant = ThreadLocalContextHolder.get(ThreadLocalContextHolder.KEY_MANDANT);
        sql = sql.replaceAll(TABLE_NAME_MANDANT_PLACEHOLDER, mandant);
        String prepedStatement = super.onPrepareStatement(sql);
        return prepedStatement;
    }
}

In my case i am using the interceptor to change the table name dynamically at runtime with a value which i set to ThreadLocal before executing any database access.

ThreadLocalContextHolder.put(ThreadLocalContextHolder.KEY_MANDANT, "EWI");
    return this.transactionStatusRepository.findOne(id);

For convinience the ThreadLocalContextHolder:

public class ThreadLocalContextHolder {

    public static String KEY_MANDANT;

    private static final ThreadLocal<Map<String,String>> THREAD_WITH_CONTEXT = new ThreadLocal<>();

    private ThreadLocalContextHolder() {}

    public static void put(String key, String payload) {
        if(THREAD_WITH_CONTEXT.get() == null){
            THREAD_WITH_CONTEXT.set(new HashMap<String, String>());
        }
        THREAD_WITH_CONTEXT.get().put(key, payload);
    }

    public static String get(String key) {
        return THREAD_WITH_CONTEXT.get().get(key);
    }

    public static void cleanupThread(){
        THREAD_WITH_CONTEXT.remove();
    }
}

Using Spring Bean configuration to wire everthing together:

@Primary
@Bean
public EntityManagerFactory entityManagerFactory(DataSource dataSource,
                                                 PersistenceProvider persistenceProvider,
                                                 Properties hibernateProperties) {

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    vendorAdapter.setGenerateDdl(true);

    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setJpaProperties(hibernateProperties);
    factory.setPackagesToScan("com.mypackage");
    factory.setDataSource(dataSource);
    factory.setPersistenceProvider(persistenceProvider);
    factory.afterPropertiesSet();

    return factory.getObject();
}

@Bean
public Properties hibernateProperties() {

    Properties jpaProperties = new Properties();
    jpaProperties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));

    jpaProperties.put("hibernate.hbm2ddl.auto",
            environment.getRequiredProperty("spring.jpa.hibernate.ddl-auto")
    );
    jpaProperties.put("hibernate.ejb.naming_strategy",
            environment.getRequiredProperty("spring.jpa.properties.hibernate.ejb.naming_strategy")
    );
    jpaProperties.put("hibernate.show_sql",
            environment.getRequiredProperty("spring.jpa.properties.hibernate.show_sql")
    );
    jpaProperties.put("hibernate.format_sql",
            environment.getRequiredProperty("spring.jpa.properties.hibernate.format_sql")
    );
    return jpaProperties;
}

@Primary
@Bean
public PersistenceProvider persistenceProvider() {
    return new InterceptorAwareHibernatePersistenceProvider();
}

@Bean
public Interceptor interceptor() {
    return new TableNameInterceptor();
}
samconny
  • 1
  • 1