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

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

我知道如何以我想要的方式创建一个Hibernate SessionFactory ,但我不想要一个Hibernate SessionFactory ,我想要一个由Hibernate SessionFactory支持的JPA EntityManagerFactory 。 给定一个EntityManagerFactory有一种获取底层SessionFactory ,但是如果你拥有的是SessionFactory并且你想要的只是一个EntityManagerFactory包装器,那么你似乎运气不好。

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

 @SuppressWarnings( "deprecation" ) EntityManagerFactory buildEntityManagerFactory( UnmodifiableMap properties, UnmodifiableCollection<Class> annotatedClasses, Interceptor interceptor ) { Ejb3Configuration cfg = new Ejb3Configuration(); for( Binding 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 properties, UnmodifiableCollection<Class> annotatedClasses, Interceptor interceptor ) { Configuration cfg = new Configuration(); for( Binding 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 ); } 

(这是一个黑客,因为我正在实例化包含org.hibernate.jpa.internal EntityManagerFactoryImpl 。)

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

所以,这是我的问题:

有没有人知道实现这个function的一种漂亮而干净的方式

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

编程方式创建一个Hibernate EntityManagerFactory ,这意味着没有配置xml文件没有使用Spring而是使用Hibernate Interceptor

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

最简单的方法是传递org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor引用,它是对“持久性单元”信息的抽象。 在正常的JPA引导中,Hibernate将在persistence.xml上构建一个PersistenceUnitDescriptor (对于JPA称之为“SE bootstrapping”)或者在javax.persistence.spi.PersistenceUnitInfo构建一个PersistenceUnitDescriptor (对于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; } ... } 

经过大量研究后我发现这个解决方案有效:

创建一个注入拦截器的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> 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()); } 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(); }