Java – @ Autowired bean used with @ valid on the controller but failed in the crud repository
I am using the user registration form to process the spring MVC hibernate JPA application. I decided to use the jsr-303 authenticator to check whether the user name already exists in the DB:
public class UniqueUsernameValidator implements ConstraintValidator<VerifyUniqueUsername,String> { @Autowired UserService userService; @Override public void initialize(VerifyUniqueUsername constraintAnnotation) { } @Override public boolean isValid(String username,ConstraintValidatorContext context) { return username!=null && userService.findByUsername(username) == null; } }
It is very simple to verify that it works well on my controller:
.... public String signup(@Valid @modelattribute("newUser") User user,BindingResult newUserBeanResult) .....
The current problem I face is that after I verify the user object, I call:
userService.save(user);
Which implements crudrepository, I get a NullPointerException For some reason, userservice is injected during validation on the controller, but when calling crudrepository Not when save()
I see similar posts as follows: @ Autowired bean null in constraintvalidator when invoked by sessionfactory getCurrentSession. Merge also has this: Hibernate validator without using autowire, but I want to know if anyone has encountered this before I think it is quite common to inject beans to access the database on the validator
As a solution, I added a null check on userservice, but it didn't feel right
>Is this expected behavior? When calling crudrepository Will these validations be activated before save() Would I like to handle "manual" hibernation events? In this case, pre insert
Solution
I finally solved this problem by instructing spring's entitymanagerfactorybean to use my validator bean (more precisely, hibernate will now use spring's validator):
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerfactorybean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> <property name="packagesToScan" value="some.packages"/> <property name="jpaPropertyMap"> <map> <entry key="javax.persistence.validation.factory" value-ref="validator" /> </map> </property> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MysqLDialect</prop> <prop key="hibernate.max_fetch_depth">3</prop> <prop key="hibernate.jdbc.fetch_size">50</prop> <prop key="hibernate.jdbc.batch_size">10</prop> <prop key="hibernate.show_sql">true</prop> </props> </property> </bean>
However, this raises a stackoverflow error:)
Obviously, the reason for this problem is that my verifier uses the finder method (findbyusername), which triggers hibernate flush, which in turn triggers authentication This infinite loop until you get the most famous exception
So... I solved this problem by changing the verifier to use entitymanager directly (instead of crud repository) and temporarily changing the flushmodetype to commit Here is an example:
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername,String> { @PersistenceContext private EntityManager em; @Autowired UserService userService; @Override public void initialize(UniqueUsername constraintAnnotation) { } @Override public boolean isValid(String username,ConstraintValidatorContext context) { try { em.setFlushMode(FlushModeType.COMMIT); return userService.findByUsername(username) == null; } finally { em.setFlushMode(FlushModeType.AUTO); } } }
This solves the problem that the verifier uses the finder function to trigger hibernate flush, which triggers the verifier, resulting in stackoverflowerror