问题
This question is specifically about programmatically creating a JPA EntityManagerFactory
backed by Hibernate 5, meaning without configuration xml files and without using Spring. Also, this question is specifically about creating an EntityManagerFactory
with a Hibernate Interceptor.
I know how to create a Hibernate SessionFactory
the way I want, but I do not want a Hibernate SessionFactory
, I want a JPA EntityManagerFactory
backed by a Hibernate SessionFactory
. Given an EntityManagerFactory
there is a way to obtain the underlying SessionFactory
, but if what you have is a SessionFactory
and all you want is an EntityManagerFactory
wrapper around it, it appears that you are out of luck.
With Hibernate version 4.2.2 Ejb3Configuration
was already deprecated, but there seemed to be no other way to programmatically create an EntityManagerFactory
, so I was doing something like this:
@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();
}
With Hibernate 4.3.0 Ejb3Configuration
was removed, so I had to make use of this 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 );
}
(It is a hack because I am instantiating EntityManagerFactoryImpl
which is in package org.hibernate.jpa.internal
.)
Now, with Hibernate 5 they have changed the constructor of EntityManagerFactoryImpl
, so the above code does not work. I can waste a few hours trying to figure out how to set things up so that I can invoke that constructor, but I am sure that after a couple of Hibernate versions, that won't work anymore, either.
So, this is my question:
Does anybody know of a nice and clean way of implementing this function
EntityManagerFactory buildEntityManagerFactory(
UnmodifiableMap<String,String> properties,
UnmodifiableCollection<Class<?>> annotatedClasses,
Interceptor interceptor )
so as to create a Hibernate EntityManagerFactory
programmatically, meaning without configuration xml files and without using Spring but with a Hibernate Interceptor ?
There is this old question: Hibernate create JPA EntityManagerFactory with out persistence.xml but it has an answer for an older version of Hibernate, which has already been anticipated in this question. That won't do, because I want it to work with Hibernate 5, and ideally, in a way which does not use anything deprecated or internal, so as to have some chances of working for a long time to come.
回答1:
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;
}
...
}
回答2:
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();
}
来源:https://stackoverflow.com/questions/32696237/jpa-with-hibernate-5-programmatically-create-entitymanagerfactory