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インターセプターを使用する必要があると思います。
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;
}
};
}
いくつかのスレッドを参考にして、次の解決策になりました。
私は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が解決されると、これは必要なくなるでしょう。
スプリングブート用の休止状態リスナーの私の簡単な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;
}
}
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固有の動作を利用できます。
こんにちは、
これを読んでください: 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バージョンを常に更新します(可能な限り最新のものを使用します)。ほとんどのコードは新しいバージョンとして冗長になることがわかります。構成をできるだけ減らすようにしてください。
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>
この同じ問題にぶつかり、小さなスプリングライブラリを作成して、すべてのセットアップを処理しました。
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.
}
}
インターセプターは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);
}
}
@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);
}
}
hibernate.session_factory.interceptor
を使用できますhibernate.ejb.interceptor
。どちらのプロパティも、おそらく後方互換性の要件のために機能します。推奨される回答の1つは、インターセプターをspring.jpa.properties.hibernate.ejb.interceptor
application.properties/ymlのプロパティ。インターセプターが複数のアプリケーションで使用されるライブラリにある場合、このアイデアは機能しない場合があります。各アプリケーションがアプリケーションを変更する必要なく、libに依存関係を追加するだけでインターセプターをアクティブにしたい場合。プロパティ。