8

我正在开发带有用户注册表单的 Spring MVC + Hibernate + JPA 应用程序,我决定使用 JSR-303 验证器来检查数据库中是否已经存在用户名:

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().

我看到了类似的帖子,例如: @Autowired bean null in ConstraintValidator when called by Sessionfactory.getCurrentSession.merge 和 this: hibernate validator without using autowire 但我想知道是否有人以前遇到过这个问题。我认为注入 bean 以访问验证器上的数据库是相当普遍的。

作为一种解决方法,我添加了对 null 的检查,userService但感觉不对。

  1. 这是预期的行为吗?这些验证是否应该在调用之前触发CrudRepository.save()
  2. 我应该处理“手动”休眠事件吗?在这种情况下pre-insert
4

2 回答 2

3

我最终通过指示 SpringEntityManagerFactoryBean使用我的验证器 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 方法触发了休眠刷新,进而触发了验证。这会无限循环,直到你得到最著名的异常。

所以...我通过将验证器更改为直接使用 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 函数触发休眠刷新的问题,这反过来又触发了验证器导致 StackOverflowError。

于 2012-12-17T23:53:01.717 回答
2

当验证逻辑被调用以响应保存方法时,它由休眠完成。验证器对象是由 hibernate 创建的,因此 spring @AutoWired 将不起作用。

解决此问题的一种选择是使用 @Configurable 注释并启用加载时间编织,以确保即使当 hibernate 实例化验证器对象时,spring 也可以将依赖项注入其中。

于 2012-07-15T17:14:00.823 回答