我正在开发一个带有用户注册表单的 Spring MVC + Hibernate + JPA 应用程序,我决定使用 JSR-303 validator 来检查用户名是否已存在于数据库中:
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;
}
}
它非常简单,并且在我的 Controller 上验证效果很好:
....
public String signup(@Valid @ModelAttribute("newUser") User user, BindingResult newUserBeanResult)
.....
我目前面临的问题是,在我验证了我的 User
对象后,我调用了:
userService.save(user);
它实现了 CrudRepository
,我得到了一个 NullPointerException
。出于某种原因,UserService
在 Controller 验证期间被注入(inject),但当我调用 CrudRepository.save()
时没有。
我看过类似的帖子,例如: @Autowired bean null in ConstraintValidator when invoked by Sessionfactory.getCurrentSession.merge 还有这个: hibernate validator without using autowire 但我想知道以前是否有人遇到过这个问题。我认为注入(inject) bean 以访问 validator 上的数据库是相当普遍的。
作为一种解决方法,我在 userService
上添加了 null 检查,但感觉不对。
- 这是预期的行为吗?这些验证是否应该在调用
CrudRepository.save()
之前触发? - 我应该“手动”处理 hibernate 事件吗?在这种情况下
预插入
最佳答案
我最终通过指示 Spring 的 EntityManagerFactoryBean
使用我的 validator bean 解决了这个问题(更准确地说,hibernate 现在将使用 Spring 的 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>
但是,这引发了 StackOverflow 错误 :)
显然,造成此问题的原因是我的 validator 使用了查找器方法 (findByUsername
) 并且查找器方法触发了 hibernate 刷新,这反过来又触发了验证。这无限循环,直到你得到最著名的异常。
所以...我通过将 validator 更改为直接使用 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);
}
}
}
这解决了 validator 使用 finder 函数触发 hibernate 刷新的问题,该刷新又触发 validator 导致 StackOverflowError。
关于java - @Autowired bean 在 Controller 上与@Valid 一起工作,但在 CRUD 存储库上失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11490075/