How to use Spring managed Hibernate interceptors i

2020-01-24 20:17发布

Is it possible to integrate Spring managed Hibernate interceptors (http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch14.html) in Spring Boot?

I'm using Spring Data JPA and Spring Data REST and need an Hibernate interceptor to act on an update of a particular field on an entity.

With standard JPA events it's not possible to get the old values, and hence I think I need to use the Hibernate interceptor.

9条回答
The star\"
2楼-- · 2020-01-24 20:53

Taking the several threads as reference I ended up with the following solution:

I am using Spring-Boot 1.2.3.RELEASE (which is the current ga at the moment)

My use case was that described in this bug (DATAREST-373).

I needed to be able to encode the password of a User @Entity upon create, and have special logic upon save. The create was very straightforward using @HandleBeforeCreate and checking the @Entity id for 0L equality.

For the save I implemented a Hibernate Interceptor which extends an EmptyInterceptor

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

    }
}

Using spring boot the documentation states that

all properties in spring.jpa.properties.* are passed through as normal JPA properties (with the prefix stripped) when the local EntityManagerFactory is created.

As many references stated, we can defined our interceptor using spring.jpa.properties.hibernate.ejb.interceptor in our Spring-Boot configuration. However I couldn't get the @Autowire PasswordEncoder to work.

So I resorted to using HibernateJpaAutoConfiguration and overriding protected void customizeVendorProperties(Map<String, Object> vendorProperties). Here is my configuration.

@Configuration
public class HibernateConfiguration extends HibernateJpaAutoConfiguration{


    @Autowired
    Interceptor userInterceptor;


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

Autowiring the Interceptor instead of allowing Hibernate to instantiate it was the key to getting it to work.

What bothers me now is that the logic is split in two, but hopefully once DATAREST-373 is resolved then this wont be necessary.

查看更多
霸刀☆藐视天下
3楼-- · 2020-01-24 21:02

Because the interceptor do not register as a spring bean,you can use a util which can get ApplicationContext instance,like this:

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

Then you can call the service in the interceptor,like this:

public class SimpleInterceptor extends EmptyInterceptor {

   @Override
   public String onPrepareStatement(String sql) {
       MyService myService=SpringContextUtil.getApplicationContext().getBean(MyService.class);
       myService.print();
    return super.onPrepareStatement(sql);
   }
 }
查看更多
孤傲高冷的网名
4楼-- · 2020-01-24 21:06

My simple one file example of hibernate listeners for 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;
    }
}
查看更多
登录 后发表回答