web-dev-qa-db-ja.com

Spring BootでSpring管理のHibernateインターセプターを使用する方法は?

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インターセプターを使用する必要があると思います。

28
Marcel Overdijk

Spring BeanでもあるHibernateインターセプターを追加する特に簡単な方法はありませんが、Hibernateによって完全に管理されているインターセプターを簡単に追加できます。これを行うには、以下をapplication.propertiesに追加します。

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

InterceptorをBeanにする必要がある場合は、独自のLocalContainerEntityManagerFactoryBeanを作成できます。 Spring Boot 1.1.4のEntityManagerFactoryBuilderは、プロパティのジェネリックに対して少し制限が強すぎるため、(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;
        }
    };
}
39
Phil Webb

いくつかのスレッドを参考にして、次の解決策になりました。

私はSpring-Boot 1.2.3.RELEASEを使用しています(現時点では現在のGAです)

私のユースケースは このバグ(DATAREST-373) で説明されているものでした。

User _@Entity_ uponcreateのパスワードをエンコードし、特別なロジックsave。作成は、_@HandleBeforeCreate_を使用し、_@Entity_ idで_0L_が等しいかどうかを確認するのが非常に簡単でした。

保存のために、 EmptyInterceptor を拡張する Hibernate Interceptor を実装しました

_@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。*のすべてのプロパティは、ローカルEntityManagerFactoryが作成されるときに、通常のJPAプロパティとして(プレフィックスが削除されて)渡されます。

多くの参考文献が述べているように、Spring-Boot構成で_spring.jpa.properties.hibernate.ejb.interceptor_を使用してインターセプターを定義できます。ただし、_@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);
    }
}
_

Hibernateにインスタンス化を許可する代わりにInterceptorを自動配線することが、それを機能させるための鍵でした。

今私を悩ませているのは、ロジックが2つに分割されていることですが、願わくば、DATAREST-373が解決されると、これは必要なくなるでしょう。

18

スプリングブート用の休止状態リスナーの私の簡単な1つのファイルの例(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 Bootではなく、Spring 4.1.1、Hibernate 4.3.11アプリケーションでも同様の問題が発生しました。

私が見つけた解決策(Hibernate EntityManagerFactoryBuilderImplコードを読んだ後)は、クラス名の代わりにBean参照をエンティティマネージャー定義のhibernate.ejb.interceptorプロパティに渡すと、Hibernateは既にインスタンス化されたBeanを使用することでした。

したがって、アプリケーションコンテキストの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

こんにちは

これを読んでください: https://github.com/spring-projects/spring-boot/commit/59d5ed58428d8cb6c6d9fb723d0e334fe3e7d9be (使用:HibernatePropertiesCustomizerインターフェイス)

[〜#〜] or [〜#〜]

単純なインターセプターの場合:

アプリケーションでこれを構成するには、次を追加するだけです:spring.jpa.properties.hibernate.ejb.interceptor = path.to.interceptor( application.propertiesで)。インターセプター自体は@ Componentである必要があります。

インターセプターが実際にBeanを使用しない限り。それ以外の場合は少し複雑ですが、解決策を提供できればうれしいです。

Application-test.propertiesに、EmptyInterceptorを追加することを忘れないでください。ロギングシステム(または使用するもの)を使用しないでください。テスト(これはあまり役に立ちません)。

これがあなたの役に立つことを願っています。

最後の注意として:Spring/Hibernateバージョンを常に更新します(可能な限り最新のものを使用します)。ほとんどのコードは新しいバージョンとして冗長になることがわかります。構成をできるだけ減らすようにしてください。

3
Rareș Flueraș

Hibernate InterceptorsとSpring Data JPAの統合方法について2日間調査した後、別のアプローチを見つけました。私のソリューションはJava configurationとxml configurationのハイブリッドですが、 this 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/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を、最初のパラメーターがリッスンするエンティティであり、2番目のパラメーターがリッスンする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.
  }
}
2
Tyler Eastman

インターセプターはSpring Beanとして登録しないため、次のようにApplicationContextインスタンスを取得できるutilを使用できます。

@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

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);
    }
}
  • Spring Boot 2.1.3.RELEASEを使用しています。
  • の代わりに hibernate.session_factory.interceptorを使用できますhibernate.ejb.interceptor。どちらのプロパティも、おそらく後方互換性の要件のために機能します。

Application.propertiesではなくHibernatePropertiesCustomizerを使用する理由

推奨される回答の1つは、インターセプターをspring.jpa.properties.hibernate.ejb.interceptor application.properties/ymlのプロパティ。インターセプターが複数のアプリケーションで使用されるライブラリにある場合、このアイデアは機能しない場合があります。各アプリケーションがアプリケーションを変更する必要なく、libに依存関係を追加するだけでインターセプターをアクティブにしたい場合。プロパティ。

0
Paulo Merson