【问题标题】:JSR-303 dependency injection and HibernateJSR-303 依赖注入和 Hibernate
【发布时间】:2011-02-12 07:56:07
【问题描述】:

春季 3.0.2, 休眠 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。问题是 EntityManager 在这个新的 ConstraintValidator 中为空。我尝试在 ConstraintValidator 中注入其他依赖项,这些依赖项始终为空。

有谁知道是否可以将依赖项注入到 ConstraintValidator 中?

【问题讨论】:

    标签: hibernate spring dependency-injection validation bean-validation


    【解决方案1】:

    在 EntityManager 中注入 Spring 上下文感知 ValidatorFactory 的最佳方法是使用 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"/>
    

    享受吧!

    【讨论】:

    • 您可能会觉得这个问题很有趣。我无法在类似的情况下解决问题。 stackoverflow.com/questions/13599821/…
    • 使用 Spring 4 + Hibernate 4.3 + HV 5.1。我花了惊人的时间来解决运行系统中存在多个 ValidationFactories 的问题。这工作正常。
    • @MartinFrey 这里也一样!通过调试hibernate代码找到解决方案后,只需到达此页面。这似乎是一个无证的“秘密”财产......
    【解决方案2】:

    虽然 JSR-303(Hibernate Validator 是参考实现)实例化了我们的自定义 ConstraintValidator,但它实际上将实际创建委托给了 ValidatorFactory。 因此,您认为使用 Spring 的 LocalValidatorFactoryBean 应该为整个应用程序中的自定义验证器启用依赖注入是正确的。

    这里的微妙之处在于,在处理实体生命周期事件(更新前、插入前、删除前)时,Hibernate 本身使用了一个单独的 ValidatorFactory,而不是在 Spring 上下文中配置的那个。我认为这是设计使然:Hibernate Validator 不知道 Spring,所以我们必须告诉 Hibernate 使用 Spring 的 LocalValidatorFactoryBean 而不是创建自己的。

    基本上,在您的 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>
    

    由于我一直在努力寻找如何做到这一点,所以我在这里整理了一个演示应用程序 on Github README 文件是不言自明的。

    【讨论】:

    • 不适用于 Hibernate 4、Spring 3.1。新版本无法设置事件监听器属性。我也在尝试基于集成器的方法。 stackoverflow.com/a/11672377/161628。但它报告了重复事件侦听器注册错误。
    【解决方案3】:

    似乎在 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 中拉出,但我没有看到任何干净的方法。

    【讨论】:

    • &lt;validation-mode&gt;NONE&lt;/validation-mode&gt; 可以解决问题。而且您甚至不必手动调用验证器。 Spring 将验证传递给 Hibernate Validator。使用 Hibernate Validator 4.1,我遇到了突然所有实体都被验证两次的问题。一次通过 Spring(自动装配)和一次通过 HV(失败,因为没有自动装配)。不过,它适用于 Hibernate Validator 4.0。
    • 以及将“javax.persistence.validation.mode”设置为“none”。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        如果您使用的是 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 - Add Hibernate Interceptor的想法

        Spring Boot - Hibernate custom constraint doesn't inject Service

        【讨论】:

          猜你喜欢
          • 2013-06-09
          • 1970-01-01
          • 2015-02-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-01
          • 2011-11-27
          相关资源
          最近更新 更多