首页 文章

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

提问于
浏览
27

是否有可能在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拦截器 .

8 回答

  • 0

    有完全由Hibernate管理的's not a particularly easy way to add a Hibernate interceptor that is also a Spring Bean but you can easily add an interceptor if it' . 为此,请将以下内容添加到 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;
            }
        };
    }
    
  • 0

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

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

    我的用例是this bug (DATAREST-373)中描述的 .

    我需要能够在 create 上编码 User @Entity 的密码,并在 save 上有特殊逻辑 . 使用 @HandleBeforeCreate 创建非常简单,并检查 @Entity id是否为 0L 相等 .

    为了保存,我实现了Hibernate Interceptor,扩展了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;
    
        }
    }
    

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

    当创建本地EntityManagerFactory时,spring.jpa.properties . *中的所有属性都作为普通的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);
        }
    }
    

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

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

  • 2

    我简单的一个用于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;
        }
    }
    
  • 3

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

    我发现(在阅读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-natured行为 .

  • 15

    在研究了如何将Hibernate Interceptor与Spring Data JPA集成两天后,我找到了另一种方法,我的解决方案是java配置和xml配置之间的混合,但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

    Hello

    给它一个读: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 ,以便在测试中不使用日志系统(或任何你想要用它的东西)(这不是很有用) .

    希望这对你有用 .

    最后一点: always update your Spring / Hibernate versions (尽可能使用最新版本),您会发现大多数代码都会变得多余,因为新版本会尽可能地减少配置 .

  • 35

    我遇到了同样的问题,并创建了一个小 spring 库来处理所有的设置 .

    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添加到第一个参数是您要侦听的实体的任何方法,第二个参数是您要监听的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.
      }
    }
    
  • 7

    因为拦截器没有注册为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);
       }
     }
    

相关问题