JPA with Hibernate 5:以编程方式创建EntityManagerFactory

时间:2015-09-21 13:15:01

标签: java hibernate jpa hibernate-entitymanager hibernate-5.x

此问题具体关于以编程方式创建由Hibernate 5支持的JPA EntityManagerFactory,意味着没有配置xml文件和< strong>不使用Spring 。此外,这个问题具体关于使用Hibernate Interceptor创建EntityManagerFactory

我知道如何按照我想要的方式创建一个Hibernate SessionFactory,但我不想要一个Hibernate SessionFactory,我想要一个由Hibernate {{1}支持的JPA EntityManagerFactory }。给定SessionFactory有一种获取基础EntityManagerFactory的方法,但如果您拥有的是SessionFactory并且您想要的只是一个SessionFactory包装,它会出现你运气不好。

使用Hibernate版本4.2.2 EntityManagerFactory已被弃用,但似乎没有其他方式以编程方式创建Ejb3Configuration,所以我做了类似的事情:

EntityManagerFactory

Hibernate 4.3.0 @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(); } 被删除了,所以我不得不利用这个hack:

Ejb3Configuration

(这是一个黑客,因为我正在实例化包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 ); } 中的EntityManagerFactoryImpl。)

现在,使用Hibernate 5,他们已经更改了org.hibernate.jpa.internal的构造函数,因此上面的代码不起作用。我可以浪费几个小时试图弄清楚如何设置,以便我可以调用该构造函数,但我确信在经过几个Hibernate版本之后,它们也不再有用了。

所以,这是我的问题:

是否有人知道实现此功能的一种干净利落的方式

EntityManagerFactoryImpl

以便以编程方式创建Hibernate EntityManagerFactory buildEntityManagerFactory( UnmodifiableMap<String,String> properties, UnmodifiableCollection<Class<?>> annotatedClasses, Interceptor interceptor ) ,这意味着 没有配置xml文件 不使用Spring 使用Hibernate Interceptor

有一个老问题:Hibernate create JPA EntityManagerFactory with out persistence.xml但它有一个旧版本的Hibernate的答案,这已经在这个问题中得到了预期。这是不可能的,因为我希望它能够与Hibernate 5一起使用,理想情况下,它不会使用任何弃用或内部的东西,以便有一段时间可以工作的机会。

2 个答案:

答案 0 :(得分:0)

最简单的方法是传递org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor引用,这是对“持久性单元”信息的抽象。在正常的JPA引导中,Hibernate将在persistence.xml上构建PersistenceUnitDescriptor(对于JPA所谓的“SE引导”)或在javax.persistence.spi.PersistenceUnitInfo上构建(对于JPA所谓的“EE引导”)。

但它是一个抽象的原因:)你可以创建自己的并传递你希望Hibernate使用的东西。这种工作的预期方式是从org.hibernate.jpa.boot.spi.Bootstrap开始,例如:

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

    ...
}

答案 1 :(得分:0)

经过大量研究,我发现此解决方案有效:

创建一个注入拦截器的PersistenceProvider:

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

创建拦截器:

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

就我而言,我正在使用拦截器来动态更改表名 在运行时带有一个我之前设置为ThreadLocal的值 执行任何数据库访问。

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

为了方便起见,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();
    }
}

使用Spring Bean配置将所有内容连接在一起:

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