JPA с Hibernate 5: программно создать EntityManagerFactory

Этот вопрос конкретно о программном создании JPA EntityManagerFactory с поддержкой Hibernate 5, что означает без конфигурационных XML-файлов и без использования Spring . Кроме того, этот вопрос конкретно о создании EntityManagerFactory с Hibernate Interceptor.

Я знаю, как создать Hibernate SessionFactory так, как я хочу, но мне не нужен Hibernate SessionFactory, мне нужен JPA EntityManagerFactory, поддерживаемый Hibernate SessionFactory. Учитывая 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 был удален, поэтому мне пришлось воспользоваться этим хаком:

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 из пакета org.hibernate.jpa.internal.)

Теперь, в Hibernate 5, они изменили конструктор EntityManagerFactoryImpl, поэтому приведенный выше код не работает. Я могу потратить несколько часов, пытаясь понять, как все настроить, чтобы вызвать этот конструктор, но я уверен, что после пары версий Hibernate это тоже больше не будет работать.

Итак, это мой вопрос:

Кто-нибудь знает хороший и чистый способ реализации этой функции?

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

чтобы создать Hibernate EntityManagerFactory программно, что означает без файлов конфигурации и без используя Spring, но с Hibernate Interceptor?

Возникает старый вопрос: Hibernate создает JPA EntityManagerFactory с out persistence.xml, но у него есть ответ для более старой версии Hibernate, который уже ожидался в этом вопросе. Это не сработает, потому что я хочу, чтобы он работал с Hibernate 5, и в идеале, таким образом, чтобы не использовать ничего устаревшего или внутреннего, чтобы иметь некоторые шансы работать в течение длительного времени.


person Mike Nakis    schedule 21.09.2015    source источник
comment
Вам действительно нужен перехватчик (они не зря затрудняют регистрацию). Что в нем такого особенного, что его нельзя заменить на JPA EventListener, и он будет работать без хаков, обходных путей и т. Д.   -  person M. Deinum    schedule 21.09.2015
comment
@ M.Deinum JPA хочет создать экземпляры моих слушателей сущностей самостоятельно, требуя, чтобы у них были конструкторы без параметров. Это, мягко говоря, недопустимо. Hibernate позволяет мне предоставить фактический экземпляр моего перехватчика, поэтому я могу сконструировать его так, как захочу. Кроме того, слушатели сущностей JPA не позволяют мне создавать экземпляры моих собственных сущностей. Опять же, Hibernate Interceptor дает мне свободу создавать экземпляры моих собственных сущностей. Это первостепенное значение. Для получения дополнительной информации см. stackoverflow.com/a/29433238/773113.   -  person Mike Nakis    schedule 21.09.2015
comment
@MikeNakis Вы пробовали связаться с группой разработчиков? Вы разрешили эту ситуацию? У меня очень похожая проблема с вашей.   -  person message    schedule 08.01.2016
comment
@message Нет, не слышал. И исправление этой проблемы больше не является моей приоритетной задачей. Но если что-то найдешь, скажи, пожалуйста. Мне было бы интересно узнать, для использования в будущем.   -  person Mike Nakis    schedule 08.01.2016


Ответы (2)


Самый простой способ - передать 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;
    }

    ...
}
person Steve Ebersole    schedule 29.08.2016
comment
Спасибо! Пройдет некоторое время, прежде чем у меня будет возможность проверить это и посмотреть, работает ли это для меня, так что держитесь. - person Mike Nakis; 31.08.2016
comment
@MikeNakis У меня были такие же проблемы (без перехватчика), которые я решил с помощью кода, аналогичного ответу в stackoverflow.com/a/ 42372648/48136. Обратите внимание, что можно избежать прямого использования HibernatePersistenceProvider, повторно используя некоторый код в javax.persistence.Persistence для поиска поставщика. - person Brice; 21.02.2017

После долгих исследований я обнаружил, что это решение работает:

Создайте 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();
}
person samconny    schedule 31.07.2018