JSR-303 dependency injection and Hibernate
Asked Answered
C

6

9

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

I am trying to inject Spring dependencies into a ConstraintValidator using:

@PersistenceContext
private EntityManager entityManager;

I have configured the application context with:

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

Which, according to the Spring documentation, should allow “custom ConstraintValidators to benefit from dependency injection like any other Spring bean”

Within the debugger I can see Spring calling getBean to create the ConstraintValidator. Later when flush triggers the preInsert, a different ConstraintValidator is created and called. The problem is the EntityManager is null within this new ConstraintValidator. I’ve tried injecting other dependencies within the ConstraintValidator and these are always null.

Does anyone know if it is possible to inject dependencies into a ConstraintValidator?

Cupidity answered 26/4, 2010 at 9:34 Comment(0)
S
13

The best way to inject a Spring context aware ValidatorFactory inside your EntityManager is by using the javax.persistence.validation.factory property. Configuration goes as follows:

<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"/>

Enjoy!

Skat answered 25/5, 2011 at 14:23 Comment(3)
You might find this question interesting. I am not able to solve the problem in a similar scenario. #13600321Bur
Using Spring 4 + Hibernate 4.3 + HV 5.1. Took me an amazing amount of time to find the solution to the problem of having multiple ValidationFactories in the running system. This works fine.Ingroup
@MartinFrey Same here! Just reach this page after find the solution by debugging hibernate code. This seems as an undocumented "secret" property...Butterwort
S
7

Although JSR-303 (Hibernate Validator being the reference implementation) instantiates our custom ConstraintValidator, it in effect delegates the actual creation to a ValidatorFactory. So you are right to think that using Spring's LocalValidatorFactoryBean should enable dependency injection for your custom validators throughout your application.

The subtlety here lies in the fact that Hibernate itself uses a separate ValidatorFactory than the one configured in your Spring context when handling entity lifecycle events (pre-update, pre-insert, pre-delete). This is by design I think: Hibernate Validator is not aware of Spring, so we must tell Hibernate to use Spring's LocalValidatorFactoryBean instead of creating its own.

Basically, something like this is required in your Spring application context 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>

Since I've struggled for a while to find out how to do it, I've put together a demo app here on Github The README file is self-explanatory.

Sustenance answered 24/11, 2010 at 4:28 Comment(1)
not working with Hibernate 4, Spring 3.1. Event Listeners property cannot be set in the new version. I am trying an Integrators based approach as well. https://mcmap.net/q/225655/-eventlisteners-using-hibernate-4-0-with-spring-3-1-0-release. But it reports a Duplicate Event Listener registered error.Bur
H
4

It seems that in JPA2, the persistence provider's validation mechanism kicks in by default on pre-persist, pre-update, etc.

It uses its own mechanism for constructing validators, Spring doesn't get involved.

The only solution I can see at the moment is disabling the out-of-the-box JPA2 validation in persistence.xml:

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

Then use Spring's LocalValidatiorFactoryBean as usual. You'll then have to call the validators manually as in the pre-JPA2 world.

UPDATE:

Just to make it complete, another solution would be to specify a constraint-validator-factory in META-INF/validation.xml.

It would be very nice if this could be Spring's SpringConstraintValidatorFactory but unfortunately, it requires an AutowireCapableBeanFactory to be passed into its constructor, but a class with no-arg constructor is expected here by JPA2.

Which leaves the option of creating own implementation of ConstraintValidatorFactory that pulls validators out of Spring, but I don't see any clean way of doing that.

Hay answered 28/4, 2010 at 11:0 Comment(2)
<validation-mode>NONE</validation-mode> does the trick. And you don't even have to call validators manually. Spring passes validation on to Hibernate Validator. With Hibernate Validator 4.1 I had the problem that suddenly all entities were validated twice. Once through Spring (with autowiring) and once through HV (failing, because not autowired). It worked with Hibernate Validator 4.0, though.Irregularity
as well as set "javax.persistence.validation.mode" to "none".Saber
F
2

For Spring 4.0.5 and Hibernate 4.3.5 I was able to solve this problem with 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>
Fewell answered 1/9, 2014 at 13:17 Comment(0)
G
1

There is also the option to pass the ValidatorFactory as property to the entity manager creation using the property javax.persistence.validation.factory. If there is a ValidatorFactory instance stored under this property, the entity manager uses this validator factory instead of creating a new one.

Gassman answered 11/5, 2010 at 15:48 Comment(0)
S
1

You can do like this if you are using 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);
            }
        };
    }
}

The idea from Spring Boot 2.0.0 M6 - Add Hibernate Interceptor

and Spring Boot - Hibernate custom constraint doesn't inject Service

Sphenic answered 18/2, 2019 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.