JPA with Hibernate 5: programmatically create EntityManagerFactory

我只是一个虾纸丫 提交于 2019-12-22 04:12:22

问题


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.


回答1:


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

    ...
}



回答2:


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


来源:https://stackoverflow.com/questions/32696237/jpa-with-hibernate-5-programmatically-create-entitymanagerfactory

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!