8

这个问题专门关于以编程方式创建EntityManagerFactory由 Hibernate 5 支持的 JPA,这意味着没有配置 xml 文件,也没有使用 Spring。此外,这个问题专门关于EntityManagerFactory 使用 Hibernate Interceptor创建一个。

我知道如何以我想要的方式创建 Hibernate SessionFactory,但我不想要 Hibernate SessionFactory,我想要一个EntityManagerFactory由 Hibernate 支持的 JPA SessionFactory。给定 aEntityManagerFactory有一种获取底层的方法SessionFactory,但是如果你拥有的是 aSessionFactory并且你想要的只是一个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.0Ejb3Configuration被删除,所以我不得不利用这个 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,因为我正在实例化EntityManagerFactoryImplpackage org.hibernate.jpa.internal。)

现在,随着 Hibernate 5 他们改变了 的构造函数EntityManagerFactoryImpl,所以上面的代码不起作用。我可能会浪费几个小时试图弄清楚如何进行设置,以便我可以调用该构造函数,但我确信在几个 Hibernate 版本之后,它也不会再工作了。

所以,这是我的问题:

有谁知道实现这个功能的好方法

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

以便以EntityManagerFactory 编程方式创建 Hibernate ,这意味着没有配置 xml 文件并且不使用 Spring使用 Hibernate 拦截器

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

4

2 回答 2

0

最简单的方法是传递org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor引用,这是对“持久性单元”信息的抽象。在正常的 JPA 引导中,Hibernate 会PersistenceUnitDescriptor在persistence.xml(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;
    }

    ...
}
于 2016-08-29T14:26:18.727 回答
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();
}
于 2018-07-31T13:30:01.910 回答