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

10

这个问题是关于以编程方式创建由Hibernate 5支持的JPA EntityManagerFactory的,意味着不使用配置xml文件和Spring。此外,这个问题是关于创建一个带有Hibernate拦截器的EntityManagerFactory。

我知道如何按照自己的要求创建Hibernate SessionFactory,但我不想要一个Hibernate SessionFactory,我想要一个由Hibernate SessionFactory支持的JPA EntityManagerFactory。如果已经有了一个EntityManagerFactory,那么有一种方法可以获得底层的SessionFactory,但如果你手头只有一个SessionFactory,而你想要的是一个包装在其周围的EntityManagerFactory,似乎就没有办法了。

在Hibernate版本4.2.2中,Ejb3Configuration已被弃用,但似乎没有其他方法来以编程方式创建EntityManagerFactory,所以我正在做类似于以下内容的事情:

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

在使用Hibernate 4.3.0时,Ejb3Configuration被删除了,因此我不得不使用这个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 );
}

(这是一种hack,因为我正在实例化org.hibernate.jpa.internal包中的EntityManagerFactoryImpl。)

现在,随着Hibernate 5的推出,EntityManagerFactoryImpl的构造函数已经发生了变化,所以以上代码不起作用了。我可以花费几个小时来尝试设置事物,以便可以调用那个构造函数,但我确定在几个Hibernate版本之后,这也不起作用了。

所以,这是我的问题:

有人知道实现此功能的一种好的且干净的方式吗?

EntityManagerFactory buildEntityManagerFactory( 
        UnmodifiableMap<String,String> properties,
        UnmodifiableCollection<Class<?>> annotatedClasses, 
        Interceptor interceptor )
为了通过Hibernate拦截器来在程序中创建EntityManagerFactory,意味着不需要配置XML文件不使用Spring。有一个关于这个问题的旧版问答:Hibernate create JPA EntityManagerFactory with out persistence.xml但是它是针对旧版Hibernate的答案,而且已经被此次提问所预料到。那种方法行不通,因为我希望它能在Hibernate 5中工作,并且最好不要使用任何过时或内部的东西,以便长期运行。

你真的需要这个拦截器吗(为了某种原因,它们很难注册)。 它有什么特别之处,不能用 JPA 事件监听器替代,并且可以在不使用黑客技巧、变通方法等的情况下正常工作呢? - M. Deinum
@M.Deinum JPA希望自己实例化我的实体监听器,要求它们具有无参构造函数。 这是不可接受的,委婉地说。 Hibernate允许我提供我的拦截器的实际实例,因此我可以按任何想要的方式构建它。 此外,JPA实体监听器不允许我实例化自己的实体。 同样,Hibernate Interceptor赋予了我实例化自己实体的自由。 这非常重要。 有关更多信息,请参见https://dev59.com/iHA85IYBdhLWcg3wHvs0#29433238 - Mike Nakis
@MikeNakis 你有试过联系开发组吗?你解决了那个问题吗?我和你的问题非常相似。 - message
@message 不,我没有。而且纠正这个问题已经不是我的优先事项了。但如果你发现了什么,请告诉我。我会很感兴趣,以备将来参考。 - Mike Nakis
2个回答

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

0

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

但这是有原因的抽象 :) 您可以创建自己的并传递您想要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;
    }

    ...
}

谢谢!我需要一些时间来检查这个并看看它是否适用于我,所以请耐心等待。 - Mike Nakis
@MikeNakis 我曾经遇到过类似的问题(没有拦截器),我通过类似于 https://dev59.com/6XI-5IYBdhLWcg3wKE-x#42372648 中的答案解决了这个问题。请注意,可以通过重用 javax.persistence.Persistence 中的一些代码来查找提供程序,从而避免直接使用 HibernatePersistenceProvider - bric3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接