如何在Spring Boot中使用Springpipe理的Hibernate拦截器?

在Spring Boot中是否可以集成Springpipe理的Hibernate拦截器( http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch14.html )?

我正在使用Spring Data JPA和Spring Data REST,并且需要Hibernate拦截器来处理实体上特定字段的更新。

使用标准的JPA事件不可能获得旧的值,因此我认为我需要使用Hibernate拦截器。

添加一个也是Spring Bean的Hibernate拦截器并不是一个特别简单的方法,但是如果它完全由Hibernate来pipe理的话,你可以很容易地添加一个拦截器。 为此,将以下内容添加到您的application.properties

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

如果你需要拦截器也是一个bean,你可以创build你自己的LocalContainerEntityManagerFactoryBean 。 Spring Boot 1.1.4中的EntityManagerFactoryBuilder对属性的generics有一点限制,所以你需要将其转换为(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; } }; } 

以几个线程作为参考,我结束了以下解决scheme:

我正在使用Spring-Boot 1.2.3.RELEASE(这是当前的ga)

我的用例是在这个bug(DATAREST-373)中描述的 。

我需要能够在创build时对User @Entity的密码进行编码,并在保存时具有特殊的逻辑。 创build是非常简单的使用@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启动文档说明

在创build本地EntityManagerFactory时,spring.jpa.properties。*中的所有属性都作为正常的JPA属性(剥离了前缀)传递。

正如许多参考文献所述,我们可以在Spring-Bootconfiguration中使用spring.jpa.properties.hibernate.ejb.interceptor来定义我们的拦截器。 但是我无法使@Autowire PasswordEncoder工作。

所以我采取了使用HibernateJpaAutoConfiguration和覆盖protected void customizeVendorProperties(Map<String, Object> vendorProperties) 。 这是我的configuration。

 @Configuration public class HibernateConfiguration extends HibernateJpaAutoConfiguration{ @Autowired Interceptor userInterceptor; @Override protected void customizeVendorProperties(Map<String, Object> vendorProperties) { vendorProperties.put("hibernate.ejb.interceptor",userInterceptor); } } 

自动assemblyInterceptor而不是让Hibernate实例化它是实现它的关键。

现在困扰我的是逻辑被分成两部分,但是希望一旦DATAREST-373解决了,那么这将不是必须的。

我简单的一个文件的春季启动(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; } } 

经过两天的研究,我发现了另一种方法,即如何将Hibernate Interceptor与Spring Data JPA集成,我的解决scheme是javaconfiguration和xmlconfiguration之间的混合,但是这个post非常有用。 所以我最终的解决scheme是:

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

数据源Javaconfiguration:

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

实体和事务pipe理器添加拦截器

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

持久性configuration文件

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

我有一个类似的问题,春季4.1.1,hibernate4.3.11应用程序 – 而不是春季启动。

解决scheme我发现(在阅读Hibernate EntityManagerFactoryBuilderImpl代码之后)是,如果你将一个bean引用而不是一个类名传递给实体pipe理器定义的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由Springpipe理,因此可以使用自动assembly和其他Spring自然行为。