wake-up-neo.net

Wie verwende ich Spring Spring Hibernate Interceptors in Spring Boot?

Ist es möglich, von Spring verwaltete Hibernate-Interceptors ( http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch14.html ) in Spring Boot zu integrieren?

Ich verwende Spring Data JPA und Spring Data REST und brauche einen Hibernate-Interceptor, um eine Aktualisierung eines bestimmten Feldes in einer Entität durchzuführen.

Bei Standard-JPA-Ereignissen ist es nicht möglich, die alten Werte abzurufen. Daher denke ich, dass ich den Interceptor für den Ruhezustand verwenden muss.

26
Marcel Overdijk

Es gibt keine besonders einfache Möglichkeit, einen Hibernate-Interceptor hinzuzufügen, der auch eine Spring Bean ist. Sie können jedoch problemlos einen Interceptor hinzufügen, wenn er vollständig von Hibernate geändert wurde. Dazu fügen Sie Ihrem application.properties Folgendes hinzu:

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

Wenn der Interceptor auch eine Bean sein soll, können Sie Ihre eigene LocalContainerEntityManagerFactoryBean erstellen. Die EntityManagerFactoryBuilder von Spring Boot 1.1.4 ist bei den generischen Eigenschaften etwas zu restriktiv. Sie müssen also in (Map) umgewandelt werden.

@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

Die verschiedenen Threads als Referenz nehmend, endete die folgende Lösung:

Ich benutze Spring-Boot 1.2.3.RELEASE (das ist die aktuelle Ga im Moment)

Mein Anwendungsfall war der in diesem Fehler (DATAREST-373) beschriebene. 

Ich musste in der Lage sein, das Kennwort einer User@Entity bei create zu kodieren und eine spezielle Logik bei save zu haben. Das Erstellen war sehr einfach mit @HandleBeforeCreate und der Überprüfung der @Entity-ID auf 0L-Gleichheit. 

Zum Speichern habe ich einen Hibernate Interceptor implementiert, der einen EmptyInterceptor erweitert

@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;

    }
}

Bei Verwendung von Spring Boot wird in der Dokumentation darauf hingewiesen 

alle Eigenschaften in spring.jpa.properties. * werden als normale JPA-Eigenschaften (mit entferntem Präfix) durchlaufen, wenn die lokale EntityManagerFactory erstellt wird.

Wie bereits erwähnt, können wir unseren Interceptor mithilfe von spring.jpa.properties.hibernate.ejb.interceptor in unserer Spring-Boot-Konfiguration definieren. Ich konnte den @Autowire PasswordEncoder jedoch nicht zum Laufen bringen.

Also habe ich auf HibernateJpaAutoConfiguration zurückgegriffen und protected void customizeVendorProperties(Map<String, Object> vendorProperties) überschrieben. Hier ist meine Konfiguration.

@Configuration
public class HibernateConfiguration extends HibernateJpaAutoConfiguration{


    @Autowired
    Interceptor userInterceptor;


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

Die Interceptor automatisch zu aktivieren, anstatt Hibernate die Instanziierung zu erlauben, war der Schlüssel, um sie zum Laufen zu bringen.

Was mich jetzt stört, ist, dass die Logik in zwei Hälften geteilt ist, aber wenn DATAREST-373 einmal gelöst ist, wird dies hoffentlich nicht nötig sein.

15

Mein einfaches Dateibeispiel für Hibernate-Listener für Spring Boot (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

Ich hatte ein ähnliches Problem mit einer Spring 4.1.1, Hibernate 4.3.11-Anwendung - nicht mit Spring Boot. 

Lösung (nachdem ich den Hibernate EntityManagerFactoryBuilderImpl-Code gelesen hatte) war, dass, wenn Sie einen Bean-Verweis anstelle eines Klassennamens an die hibernate.ejb.interceptor-Eigenschaft der Entitätsmanager-Definition übergeben, Hibernate das bereits instantiierte Bean verwendet.

In meiner EntityManager-Definition im Anwendungskontext hatte ich so etwas:

<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> 

Der auditInterceptor wird von Spring verwaltet. Daher stehen ihm automatische Einstellungen und andere Spring-Natured-Verhalten zur Verfügung.

3
BhathiyaW

Ich habe einen anderen Ansatz gefunden, nachdem ich zwei Tage recherchiert hatte, wie Hibernate Interceptors in Spring Data JPA integriert werden. Meine Lösung ist ein Hybrid aus Java-Konfiguration und XML-Konfiguration, aber this post war sehr nützlich. Meine endgültige Lösung war also:

AuditLogInterceptor-Klasse:

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;
   }

}

Datenquelle Java-Konfiguration:

@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;
}

Entity- und Transaction Manager fügen den Interceptor hinzu

<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" />

persistenz-Konfigurationsdatei

     <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

Hallo,

Lesen Sie dies wie folgt: https://github.com/spring-projects/spring-boot/commit/59d5ed58428d8cb6c6d6f9fb723d0e334fe3e7d9be (use: HibernatePropertiesCustomizer interface)

ODER

Für einfachen Interceptor:

Um dies in Ihrer Anwendung zu konfigurieren, müssen Sie einfach Folgendes hinzufügen: spring.jpa.properties.hibernate.ejb.interceptor = path.to.interceptor (in application.properties). Der Interceptor selbst sollte @Component sein.

Solange der Interceptor tatsächlich keine Beans verwendet. Ansonsten ist es etwas komplizierter, aber ich würde die Lösung gerne anbieten.

Vergessen Sie nicht, in application-test.properties einen EmptyInterceptor hinzuzufügen, um das Protokollierungssystem (oder was auch immer Sie es verwenden möchten) in Tests nicht zu verwenden (was nicht sehr hilfreich wäre).

Ich hoffe, das war für Sie von Nutzen.

Als letzten Hinweis: Aktualisieren Sie Ihre Spring-/Hibernate-Versionen (verwenden Sie möglichst die neueste), und Sie werden feststellen, dass der meiste Code überflüssig wird, da neuere Versionen versuchen, die Konfigurationen so weit wie möglich zu reduzieren.

2
Rareș Flueraș

Da sich der Interceptor nicht als Spring Bean registriert, können Sie ein util verwenden, das eine ApplicationContext-Instanz erhält, wie folgt:

@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;
   }
}

Dann können Sie den Dienst im Interceptor wie folgt aufrufen:

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

Lösung mit Spring Boot 2

@Component
public class MyInterceptorRegistration implements HibernatePropertiesCustomizer {

    @Autowired
    private MyInterceptor myInterceptor;

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put("hibernate.session_factory.interceptor", myInterceptor);
    }
}
  • Ich verwende Spring Boot 2.1.3.RELEASE.
  • Anstatt hibernate.session_factory.interceptor Sie können hibernate.ejb.interceptor. Beide Eigenschaften funktionieren wahrscheinlich aufgrund einer Abwärtskompatibilitätsanforderung.

Warum HibernatePropertiesCustomizer anstelle von application.properties?

Eine vorgeschlagene Antwort besteht darin, Ihren Abfangjäger im spring.jpa.properties.hibernate.ejb.interceptor Eigenschaft in application.properties/yml. Diese Idee funktioniert möglicherweise nicht, wenn sich Ihr Abfangjäger in einer Bibliothek befindet, die von mehreren Anwendungen verwendet wird. Sie möchten, dass Ihr Interceptor durch Hinzufügen einer Abhängigkeit zu Ihrer Bibliothek aktiviert wird, ohne dass jede Anwendung ihre Anwendung ändern muss. Eigenschaften.

0
Paulo Merson

Ich bin auf das gleiche Problem gestoßen und habe am Ende eine kleine Frühlingsbibliothek erstellt, um alle Einstellungen zu erledigen.

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

Wenn Sie Spring Boot verwenden, fügen Sie einfach die Abhängigkeit hinzu:

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

Fügen Sie dann die Annotation @HibernateEventListener zu jeder Methode hinzu, bei der der erste Parameter die Entität ist, die Sie abhören möchten, und der zweite Parameter das Hibernate-Ereignis, auf das Sie abhören möchten. Ich habe auch die statische util-Funktion getPropertyIndex hinzugefügt, um den Zugriff auf die bestimmte Eigenschaft, die Sie überprüfen möchten, einfacher zu erhalten. Sie können jedoch auch nur das rohe Hibernate-Ereignis betrachten.

@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