@Autowired bean在控制器上与@Valid一起使用,但对于CRUD存储库失败


问题内容

我正在使用带有用户注册表的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()

但是我想知道以前是否有人遇到过这种情况。我认为注入bean来访问验证器上的数据库是相当普遍的。

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

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

问题答案:

我最终通过指示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>

但是,这引发了错误:)

显然,此问题的原因是我的验证器正在使用finder方法(findByUsername),并且finder方法触发了hibernate刷新,这又触发了验证。这将无限循环,直到您获得最著名的异常为止。

所以…我通过更改验证器以直接使用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);
           }    
    }
}

这解决了验证器使用查找程序功能触发hibernate刷新的问题,而hibernate刷新又触发了验证器,从而导致StackOverflowError。