JSR-303依赖注入和Hibernate

9

Spring 3.0.2,Hibernate 3.5.0,Hibernate-Validator 4.0.2.GA。

我正在尝试使用以下方法将Spring依赖项注入到ConstraintValidator中:

@PersistenceContext
private EntityManager entityManager;

我已经配置了应用程序上下文,具体如下:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

根据Spring文档,应该允许“自定义ConstraintValidators像任何其他Spring bean一样受益于依赖注入”。在调试器中,我可以看到Spring调用getBean来创建ConstraintValidator。稍后,当刷新触发preInsert时,会创建并调用不同的ConstraintValidator。问题是在这个新的ConstraintValidator中,EntityManager为空。我尝试在ConstraintValidator中注入其他依赖项,但这些依赖项总是为空。有人知道是否可能向ConstraintValidator注入依赖项吗?
6个回答

13

将Spring上下文感知的ValidatorFactory注入到EntityManager中的最佳方法是使用javax.persistence.validation.factory属性。配置如下:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 <property name="dataSource" ref="dataSource" />
 <property name="jpaVendorAdapter">
  <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
   <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" />              
  </bean>
 </property>
 <property name="jpaPropertyMap">
  <map>
   <entry key="javax.persistence.validation.factory" value-ref="validator" />               
  </map>
 </property>
</bean>

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

享受吧!


你可能会觉得这个问题很有趣。在类似的情况下,我无法解决这个问题。https://dev59.com/lmvXa4cB1Zd3GeqPImf1 - Rohit Banga
使用Spring 4 + Hibernate 4.3 + HV 5.1。花费了我惊人的时间来解决在运行系统中有多个ValidationFactories的问题。这个方法很好用。 - Martin Frey
@MartinFrey 我也是!在调试Hibernate代码后找到了解决方案,才来到这个页面。这似乎是一个未记录的“秘密”属性... - Beccari

7
尽管JSR-303(Hibernate Validator是其参考实现)实例化了我们的自定义ConstraintValidator,但实际上它将创建工作委托给了ValidatorFactory。因此,您认为使用Spring的LocalValidatorFactoryBean应该使您的自定义验证器能够在整个应用程序中进行依赖注入,这一点是正确的。
这里的微妙之处在于,当处理实体生命周期事件(pre-update、pre-insert、pre-delete)时,Hibernate本身使用与在Spring上下文中配置的ValidatorFactory不同的ValidatorFactory。我认为这是出于设计考虑:Hibernate Validator不知道Spring,因此我们必须告诉Hibernate使用Spring的LocalValidatorFactoryBean而不是创建自己的ValidatorFactory。
基本上,在Spring应用程序上下文XML中需要类似于以下内容:
<!-- The LocalValidatorFactoryBean is able to instantiate custom validators and inject autowired dependencies. -->
<bean id="validator"
      class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

<!--
   This is the key to link Spring's injection to Hibernate event-based validation.
   Notice the first constructor argument, this is our Spring ValidatorFactory instance.
  -->
<bean id="beanValidationEventListener" class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener">
    <constructor-arg ref="validator"/>
    <constructor-arg ref="hibernateProperties"/>
</bean>

<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    ...

    <!--
      A reference to our custom BeanValidationEventListener instance is pushed for all events.
      This can of course be customized if you only need a specific event to trigger validations.
    -->
    <property name="eventListeners">
        <map>
            <entry key="pre-update" value-ref="beanValidationEventListener"/>
            <entry key="pre-insert" value-ref="beanValidationEventListener"/>
            <entry key="pre-delete" value-ref="beanValidationEventListener"/>
        </map>
    </property>
</bean>

因为我曾经苦苦搜寻如何做到这一点,所以我在Github上创建了一个演示应用程序。README文件很容易理解。


1
无法与Hibernate 4,Spring 3.1一起使用。新版本中无法设置Event Listeners属性。我也尝试了基于Integrators的方法。https://dev59.com/Lmoy5IYBdhLWcg3wWcqh#11672377。但它报告了一个重复的事件监听器注册错误。 - Rohit Banga

4

在JPA2中,似乎默认情况下持久性提供程序的验证机制会在pre-persist、pre-update等操作上启动。

它使用自己的机制构建验证器,Spring不参与其中。

目前我唯一能看到的解决方案是在persistence.xml中禁用开箱即用的JPA2验证:

<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="hibernate.show_sql" value="true"/>
      <!-- other props -->
    </properties>
  <validation-mode>NONE</validation-mode>
</persistence-unit>

然后像往常一样使用Spring的LocalValidatiorFactoryBean。您需要手动调用验证器,就像在JPA2之前的世界中一样。
更新:
为了完整起见,另一个解决方案是在META-INF/validation.xml中指定constraint-validator-factory。
如果这可以是Spring的SpringConstraintValidatorFactory,那将非常好,但不幸的是,它需要在其构造函数中传递一个AutowireCapableBeanFactory,但是JPA2在此处期望一个带有无参构造函数的类。
这留下了创建自己的ConstraintValidatorFactory实现的选项,该实现从Spring中提取验证器,但我没有看到任何清晰的方法来做到这一点。

<validation-mode>NONE</validation-mode>就可以解决问题。而且你甚至不需要手动调用验证器。Spring会将验证传递给Hibernate Validator。 在使用Hibernate Validator 4.1时,我遇到了一个问题,所有实体都被验证了两次。一次是通过Spring(自动装配),另一次是通过HV(失败,因为没有自动装配)。 但在Hibernate Validator 4.0中可以正常工作。 - Koraktor
同时将 "javax.persistence.validation.mode" 设置为 "none"。 - Matt

2

对于Spring 4.0.5和Hibernate 4.3.5,我能够通过EL解决这个问题:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
...
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    ...
    <property name="hibernateProperties">
        <props>
            ...
            <prop key="javax.persistence.validation.factory">#{validator}</prop>
        </props>
    </property>
</bean>

1
如果您使用的是Spring Boot 2.1.0+,可以这样做:
@Configuration
@Lazy
class SpringValidatorConfiguration {


    @Bean
    @Lazy
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(final Validator validator) {
        return new HibernatePropertiesCustomizer() {

            @Override
            public void customize(Map<String, Object> hibernateProperties) {
                hibernateProperties.put("javax.persistence.validation.factory", validator);
            }
        };
    }
}

这个想法来自于Spring Boot 2.0.0 M6 - 添加Hibernate拦截器

以及Spring Boot - Hibernate自定义约束不注入服务


1

还有一种选项是将ValidatorFactory作为属性传递给实体管理器创建,使用属性javax.persistence.validation.factory。如果在此属性下存储了ValidatorFactory实例,则实体管理器将使用此验证工厂而不是创建新的验证工厂。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接