it-swarm.com.ru

Как использовать управляемые Spring перехватчики Hibernate в Spring Boot?

Возможно ли интегрировать управляемые Spring перехватчики Hibernate ( http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch14.html ) в Spring Boot?

Я использую Spring Data JPA и Spring Data REST и мне нужен перехватчик Hibernate для обновления определенного поля в объекте.

Со стандартными событиями JPA невозможно получить старые значения, и поэтому я думаю, что мне нужно использовать перехватчик Hibernate.

26
Marcel Overdijk

Нет особенно простого способа добавить перехватчик Hibernate, который также является Spring Bean, но вы можете легко добавить перехватчик, если он полностью управляется Hibernate. Для этого добавьте в свой application.properties следующее:

spring.jpa.properties.hibernate.ejb.interceptor=my.package.MyInterceptorClassName

Если вам нужно, чтобы Interceptor также был бином, вы можете создать свой собственный LocalContainerEntityManagerFactoryBean. EntityManagerFactoryBuilder из Spring Boot 1.1.4 немного ограничен родовыми свойствами, поэтому вам нужно привести к (Map), мы посмотрим на исправление для 1.2.

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
        EntityManagerFactoryBuilder factory, DataSource dataSource,
        JpaProperties properties) {
    Map<String, Object> jpaProperties = new HashMap<String, Object>();
    jpaProperties.putAll(properties.getHibernateProperties(dataSource));
    jpaProperties.put("hibernate.ejb.interceptor", hibernateInterceptor());
    return factory.dataSource(dataSource).packages("sample.data.jpa")
            .properties((Map) jpaProperties).build();
}

@Bean
public EmptyInterceptor hibernateInterceptor() {
    return new EmptyInterceptor() {
        @Override
        public boolean onLoad(Object entity, Serializable id, Object[] state,
                String[] propertyNames, Type[] types) {
            System.out.println("Loaded " + id);
            return false;
        }
    };
}
35
Phil Webb

Взяв за основу несколько потоков, я получил следующее решение:

Я использую Spring-Boot 1.2.3.RELEASE (текущее значение ga на данный момент)

Мой вариант использования был описан в эта ошибка (DATAREST-373)

Мне нужно было иметь возможность кодировать пароль User@Entity при create и иметь специальную логику при save. Создание было очень простым с использованием @HandleBeforeCreate и проверкой идентификатора @Entity на равенство 0L

Для сохранения я реализовал Hibernate Interceptor , который расширяет EmptyInterceptor

@Component
class UserInterceptor extends EmptyInterceptor{

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {

        if(!(entity instanceof User)){
            return false;
        }

        def passwordIndex = propertyNames.findIndexOf { it == "password"};

        if(entity.password == null && previousState[passwordIndex] !=null){

            currentState[passwordIndex] = previousState[passwordIndex];

        }else{
            currentState[passwordIndex] = passwordEncoder.encode(currentState[passwordIndex]);
        }

        return true;

    }
}

При использовании весенней загрузки в документации говорится, что 

все свойства в spring.jpa.properties. * передаются как обычные свойства JPA (с удаленным префиксом) при создании локального EntityManagerFactory.

Как указывалось во многих ссылках, мы можем определить наш перехватчик, используя spring.jpa.properties.hibernate.ejb.interceptor в нашей конфигурации Spring-Boot. Однако я не мог заставить @Autowire PasswordEncoder работать.

Поэтому я прибег к использованию HibernateJpaAutoConfiguration и переопределению protected void customizeVendorProperties(Map<String, Object> vendorProperties). Вот моя конфигурация.

@Configuration
public class HibernateConfiguration extends HibernateJpaAutoConfiguration{


    @Autowired
    Interceptor userInterceptor;


    @Override
    protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
        vendorProperties.put("hibernate.ejb.interceptor",userInterceptor);
    }
}

Автоматическое подключение Interceptor вместо того, чтобы позволить Hibernate создавать его экземпляр, было ключом к тому, чтобы заставить его работать.

Что меня сейчас беспокоит, так это то, что логика разделена на две части, но, надеюсь, после того, как DATAREST-373 будет решен, это не понадобится.

15
Fotis Paraskevopoulos

Мой простой однофайловый пример слушателей гибернации для весенней загрузки (spring-boot-starter 1.2.4.RELEASE)

import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.*;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.jpa.HibernateEntityManagerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.EntityManagerFactory;

@Component
public class UiDateListener implements PostLoadEventListener, PreUpdateEventListener {
    @Inject EntityManagerFactory entityManagerFactory;

    @PostConstruct
    private void init() {
        HibernateEntityManagerFactory hibernateEntityManagerFactory = (HibernateEntityManagerFactory) this.entityManagerFactory;
        SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl) hibernateEntityManagerFactory.getSessionFactory();
        EventListenerRegistry registry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);
        registry.appendListeners(EventType.POST_LOAD, this);
        registry.appendListeners(EventType.PRE_UPDATE, this);
    }

    @Override
    public void onPostLoad(PostLoadEvent event) {
        final Object entity = event.getEntity();
        if (entity == null) return;

        // some logic after entity loaded
    }

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        final Object entity = event.getEntity();
        if (entity == null) return false;

        // some logic before entity persist

        return false;
    }
}
7
Sllouyssgort

У меня была похожая проблема с приложением Spring 4.1.1, Hibernate 4.3.11, а не с Spring Boot. 

Решение, которое я обнаружил (после прочтения кода Hibernate EntityManagerFactoryBuilderImpl), заключается в том, что если вы передадите ссылку на компонент вместо имени класса в свойство hibernate.ejb.interceptor определения менеджера сущностей, Hibernate будет использовать этот уже созданный экземпляр компонента.

Так что в моем определении entityManager в контексте приложения у меня было что-то вроде этого:

<bean id="auditInterceptor" class="com.something.AuditInterceptor" />

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" 
          ...> 
        <property name="jpaProperties"> 
            <map>
                ...
                <entry key="hibernate.ejb.interceptor">
                    <ref bean="auditInterceptor" />
                </entry>
                ...
            </map>
        </property> 
    </bean> 

AuditInterceptor управляется Spring, поэтому для него будет доступно автоматическое подключение и другие поведения Spring.

3
BhathiyaW

Я нашел другой подход после двухдневного исследования о том, как интегрировать перехватчики Hibernate с Spring Data JPA, мое решение представляет собой гибрид конфигурации Java и конфигурации xml, но этот post был очень полезен. Итак, мое окончательное решение было:

Класс AuditLogInterceptor:

public class AuditLogInterceptor extends EmptyInterceptor{

    private int updates;

    //interceptor for updates
    public boolean onFlushDirty(Object entity,
                            Serializable id,
                            Object[] currentState,
                            Object[] previousState,
                            String[] propertyNames,
                            Type[] types) {

        if ( entity instanceof Auditable ) {
            updates++;
            for ( int i=0; i < propertyNames.length; i++ ) {
                if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
                    currentState[i] = new Date();
                    return true;
                }
            }
        }
        return false;
   }

}

Конфигурация источника данных Java:

@Bean
DataSource dataSource() {

    //Use JDBC Datasource 
    DataSource dataSource = new DriverManagerDataSource();

        ((DriverManagerDataSource)dataSource).setDriverClassName(jdbcDriver);
        ((DriverManagerDataSource)dataSource).setUrl(jdbcUrl);
        ((DriverManagerDataSource)dataSource).setUsername(jdbcUsername);
        ((DriverManagerDataSource)dataSource).setPassword(jdbcPassword);                    

    return dataSource;
}

Менеджеры сущностей и транзакций, добавляющие перехватчик

<bean id="entityManagerFactory"
         class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
         p:persistenceUnitName="InterceptorPersistentUnit" p:persistenceXmlLocation="classpath:audit/persistence.xml"
         p:dataSource-ref="dataSource" p:jpaVendorAdapter-ref="jpaAdapter">
         <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
         </property>              
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
                 p:entityManagerFactory-ref="entityManagerFactory" />

<bean id="jpaAdapter"
                 class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
                 p:database="Oracle" p:showSql="true" />

файл конфигурации постоянства

     <persistence-unit name="InterceptorPersistentUnit">

             <class>com.app.CLASSTOINTERCEPT</class>           

             <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>

             <properties>
             <property name="hibernate.ejb.interceptor"
                      value="com.app.audit.AuditLogInterceptor" />
             </properties>
     </persistence-unit>
2
kristianva

Привет,

Прочитайте это: https://github.com/spring-projects/spring-boot/commit/59d5ed58428d8cb6c6d9fb723d0e334fe3e7d9be (используйте: интерфейс HibernatePropertiesCustomizer)

ИЛИ ЖЕ

Для простого перехватчика:

Чтобы настроить это в вашем приложении, вам просто нужно добавить: spring.jpa.properties.hibernate.ejb.interceptor = path.to.interceptor (в application.properties). Сам перехватчик должен быть @Component.

До тех пор, пока перехватчик фактически не использует бины. В противном случае это немного сложнее, но я был бы более чем рад предложить решение.

Не забудьте добавить в application-test.properties EmptyInterceptor, чтобы не использовать систему ведения журнала (или то, для чего вы хотите ее использовать) в тестах (что было бы не очень полезно).

Надеюсь, что это было полезно для вас.

И последнее замечание: всегда обновляйте версии Spring/Hibernate (используйте самые последние версии, насколько это возможно), и вы увидите, что большая часть кода станет избыточной по мере того, как более новые версии будут пытаться максимально уменьшить конфигурации.

2
Rareș Flueraș

Поскольку перехватчик не регистрируется как пружинный компонент, вы можете использовать утилиту, которая может получить экземпляр ApplicationContext, например так:

@Component
public class SpringContextUtil implements ApplicationContextAware {

   private static ApplicationContext applicationContext;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) 
   throws BeansException {
      SpringContextUtil.applicationContext=applicationContext;
   }

   public static ApplicationContext getApplicationContext() {
      return applicationContext;
   }
}

Затем вы можете позвонить в службу перехватчика, например так:

public class SimpleInterceptor extends EmptyInterceptor {

   @Override
   public String onPrepareStatement(String sql) {
       MyService myService=SpringContextUtil.getApplicationContext().getBean(MyService.class);
       myService.print();
    return super.onPrepareStatement(sql);
   }
 }
0
Null

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

https://github.com/teastman/spring-data-hibernate-event

Если вы используете Spring Boot, вы просто добавляете зависимость:

<dependency>
  <groupId>io.github.teastman</groupId>
  <artifactId>spring-data-hibernate-event</artifactId>
  <version>1.0.0</version>
</dependency>

Затем добавьте аннотацию @HibernateEventListener в любой метод, где первый параметр - это объект, который вы хотите прослушать, а второй параметр - событие Hibernate, которое вы хотите прослушать. Я также добавил статическую функцию util getPropertyIndex, чтобы упростить получение доступа к определенному свойству, которое вы хотите проверить, но вы также можете просто посмотреть на необработанное событие Hibernate.

@HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
  int index = getPropertyIndex(event, "name");
  if (event.getOldState()[index] != event.getState()[index]) {
    // The name changed.
  }
}
0
Tyler Eastman