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

Mar*_*ijk 32 hibernate spring-data spring-data-jpa spring-data-rest spring-boot

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

我正在使用Spring Data JPA和Spring Data REST,并且需要一个Hibernate拦截器来对实体上的特定字段进行更新.

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

Phi*_*ebb 41

没有一种特别简单的方法来添加一个也是Spring Bean的Hibernate拦截器,但如果它完全由Hibernate管理,你可以轻松添加一个拦截器.为此,请将以下内容添加到您的application.properties:

spring.jpa.properties.hibernate.ejb.interceptor=my.package.MyInterceptorClassName
Run Code Online (Sandbox Code Playgroud)

如果你需要Interceptor也是一个bean,你可以创建自己的LocalContainerEntityManagerFactoryBean.在EntityManagerFactoryBuilder从春天引导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;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

  • @PhilWebb还有2016年的方式吗?或者注入`EntityListener`? (8认同)

Fot*_*los 19

以几个线程为参考,我得到了以下解决方案:

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

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

我需要能够User @Entity创建时对密码进行编码,并在保存时具有特殊逻辑.创建非常简单,使用@HandleBeforeCreate和检查@Entityid是否0L相等.

为了保存,我实现了一个扩展EmptyInterceptorHibernate 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;

    }
}
Run Code Online (Sandbox Code Playgroud)

使用spring boot文档说明了这一点

当创建本地EntityManagerFactory时,spring.jpa.properties.*中的所有属性都作为普通的JPA属性(带有前缀剥离)传递.

正如许多参考文献所述,我们可以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);
    }
}
Run Code Online (Sandbox Code Playgroud)

自动装配Interceptor而不是允许Hibernate实例化它是使其工作的关键.

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


Sll*_*ort 7

我简单的一个用于spring boot的hibernate监听器的文件示例(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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Pau*_*son 7

使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 我正在使用Spring Boot 2.1.7.RELEASE。
  • 代替hibernate.session_factory.interceptor您可以使用hibernate.ejb.interceptor。这两个属性都可能由于向后兼容性要求而起作用。

为什么选择HibernatePropertiesCustomizer而不是application.properties

一个建议的答案是spring.jpa.properties.hibernate.ejb.interceptor在application.properties/yml 中的属性中指示您的拦截器。如果您的拦截器位于将由多个应用程序使用的库中,则此想法可能行不通。您希望通过仅向lib添加依赖项来激活拦截器,而无需每个应用程序更改其application.properties

  • `hibernate.ejb.interceptor` 在 Springboot 2 中也会抛出一个 `deprecated` 警告 (3认同)

Bha*_*yaW 5

我在 Spring 4.1.1、Hibernate 4.3.11 应用程序中遇到了类似的问题 - 而不是 Spring Boot。

我发现的解决方案(在阅读 Hibernate EntityManagerFactoryBuilderImpl 代码之后)是,如果您将 bean 引用而不是类名传递给hibernate.ejb.interceptor实体管理器定义的属性,Hibernate 将使用已经实例化的 bean。

因此,在应用程序上下文中的实体管理器定义中,我有这样的内容:

<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> 
Run Code Online (Sandbox Code Playgroud)

auditInterceptor 由 Spring 管理,因此自动装配和其他 Spring 特性的行为将可供它使用。


Rar*_*raș 5

你好

读一下:https://github.com/spring-projects/spring-boot/commit/59d5ed58428d8cb6c6d9fb723d0e334fe3e7d9be(使用:HibernatePropertiesCustomizer接口)

或者

对于简单的拦截器:

为了在您的应用程序中配置它,您只需添加:spring.jpa.properties.hibernate.ejb.interceptor = path.to.interceptor(在 application.properties 中)。拦截器本身应该是@Component

只要拦截器实际上不使用任何 beans。否则会有点复杂,但我很乐意提供解决方案。

不要忘记在 application-test.properties 中添加一个EmptyInterceptor ,以便在测试中不使用日志系统(或任何您想要使用它的东西)(这不会很有帮助)。

希望这对您有用。

最后一点:始终更新您的 Spring / Hibernate 版本(尽可能使用最新版本),您会发现大多数代码将变得多余,因为新版本会尝试尽可能减少配置。