我正在使用用户注册表单处理Spring MVC + Hibernate + JPA应用程序,我决定使用JSR-303验证程序检查用户名是否已存在于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;
}
}
这非常简单,验证在我的控制器上运行得很好:
....
public String signup(@Valid @ModelAttribute("newUser") User user, BindingResult newUserBeanResult)
.....
我面临的当前问题是,在我验证了User
对象后,我打电话给:
userService.save(user);
哪个实现CrudRepository
,我得到NullPointerException
。出于某种原因,UserService
在控制器验证期间注入CrudRepository.save()
,但在调用userService
时不。
我看到类似的帖子如下: @Autowired bean null in ConstraintValidator when invoked by Sessionfactory.getCurrentSession.merge 还有这个: hibernate validator without using autowire 但我想知道是否有人之前遇到过此事。我认为注入bean来访问验证器上的数据库是相当普遍的。
作为一种解决方法,我在CrudRepository.save()
上添加了空检查,但感觉不对。
pre-insert
之前,这些验证是否会被解雇?答案 0 :(得分:3)
我最终通过指示Spring的EntityManagerFactoryBean
使用我的验证器bean来解决这个问题(更准确地说,hibernate现在将使用Spring的验证器):
<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>
但是,这引发了StackOverflow错误:)
显然,这个问题的原因是我的验证器使用的是finder方法(findByUsername
),finder方法触发了hibernate flush,后者又触发了验证。这无限循环,直到你得到最着名的例外。
所以...我通过更改验证器直接使用EntityManager(而不是CRUD存储库)来修复此问题,并暂时将FlushModeType更改为COMMIT。这是一个例子:
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);
}
}
}
这解决了验证器使用finder函数触发hibernate flush的问题,而hibernate flush又触发了验证器导致StackOverflowError。
答案 1 :(得分:2)
当响应save方法调用验证逻辑时,它由hibernate完成。验证器对象由hibernate创建,因此spring @AutoWired将无法工作。
解决这个问题的一个选择是使用@Configurable注释并启用加载时间编织,以确保即使在hibernate实例化验证器对象时,spring也会将依赖项注入其中。