JPA with Hibernate 5: programmatically create EntityManagerFactory

心不动则不痛 提交于 2019-12-05 04:29:00

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

    ...
}

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