JSR303自定义验证器被调用两次

时间:2014-07-25 12:26:08

标签: java spring validation jpa bean-validation

我正在使用Spring MVC创建一个网站,并且为了持久性我使用Spring Data JPA和Hibernate 4作为我的JPA提供程序。目前正在使用Hibernate Validator处理验证。我有一个问题,我的验证器被调用两次,我无法弄清楚为什么。这是一个问题的主要原因是因为第二轮,依赖关系没有自动连接到验证器,我得到一个空指针异常。 以下是导致失败的呼叫序列:

  1. 提交注册表单,首先调用NotDefaultSectValidator并成功完成用户对象上的'whereDidYouHearAboutUs'字段。
  2. 接下来调用UniqueUsernameValidator并成功完成“用户名”字段验证。
  3. 控制器上的'addUserFromForm'方法启动,并在bindingResults对象中找不到任何错误。
  4. 然后在UserService类上调用'addUser'方法。此方法到达'userRepository.save(user);'行但之后不要立即运行'print.ln'行。单步执行此行将返回到“NotDefaultSectValidator”断点。这是第二次完成,我重新输入第二个验证器'UniqueUsernameValidator'。这里我得到一个空指针异常,因为出于某种原因,Spring第二次无法在DAO中自动装配。
  5. 任何人都可以阐明验证器被调用两次的原因,特别是为什么要跨越'userRepository.save(user);'回到这些验证器?

    非常感谢

    这是我的user.java类

    package com.dating.domain;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.PrePersist;
    import javax.persistence.PreUpdate;
    import javax.persistence.Table;
    import javax.persistence.Transient;
    import javax.validation.constraints.Pattern;
    import javax.validation.constraints.Size;
    
    import org.hibernate.annotations.Type;
    import org.hibernate.validator.constraints.Email;
    import org.hibernate.validator.constraints.NotEmpty;
    import org.joda.time.LocalDate;
    import org.springframework.format.annotation.DateTimeFormat;
    
    import com.dating.annotation.NotDefaultSelect;
    import com.dating.annotation.UniqueUsername;
    
    @Entity
    @Table(name = "dating.user")
    public class User {
    
        @Id
        @Column(name = "id")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "username", unique = true)
        @NotEmpty
        @Pattern(regexp = "^[a-zA-Z0-9]*$")
        @UniqueUsername
        private String username;
    
        @Column(name = "password", nullable = false)
        @NotEmpty
        @Size(min = 8)
        private String password;
    
        @Column(name = "first_name", nullable = false)
        @NotEmpty
        private String firstName;
    
        @Column(name = "last_name", nullable = false)
        @NotEmpty
        private String lastName;
    
        @Transient
        private String fullName;
    
        @Column(name = "email", nullable = false)
        @NotEmpty
        @Email
        private String email;
    
        @Column(name = "gender", nullable = false)
        @NotEmpty
        private String gender;
    
        @Column(name = "date_of_birth", nullable = false)
        @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
        @DateTimeFormat(pattern = "dd/MM/yyyy")
        private LocalDate dateOfBirth;
    
        @Column(name = "join_date", nullable = false)
        @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
        private LocalDate joinDate;
    
        @Column(name = "where_did_you_hear_about_us", nullable = false)
        @NotDefaultSelect
        private String whereDidYouHearAboutUs;
    
        @Column(name = "enabled")
        private boolean enabled;
    
        @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
        @JoinTable(name = "dating.user_roles", joinColumns = { @JoinColumn(name = "user_id", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "role_id", nullable = false, updatable = false) })
        private Set<Role> roles = new HashSet<Role>();
    
        @Column(name = "created_time", nullable = false)
        @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
        private LocalDate createdTime;
    
        @Column(name = "modification_time", nullable = false)
        @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
        private LocalDate modificationTime;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getFullName() {
            return firstName + " " + lastName;
        }
    
        public void setFullName(String fullName) {
            this.fullName = fullName;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        public LocalDate getDateOfBirth() {
            return dateOfBirth;
        }
    
        public void setDateOfBirth(LocalDate dateOfBirth) {
            this.dateOfBirth = dateOfBirth;
        }
    
        public LocalDate getJoinDate() {
            return joinDate;
        }
    
        public void setJoinDate(LocalDate joinDate) {
            this.joinDate = joinDate;
        }
    
        public String getWhereDidYouHearAboutUs() {
            return whereDidYouHearAboutUs;
        }
    
        public void setWhereDidYouHearAboutUs(String whereDidYouHearAboutUs) {
            this.whereDidYouHearAboutUs = whereDidYouHearAboutUs;
        }
    
        public boolean isEnabled() {
            return enabled;
        }
    
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    
        public Set<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(Set<Role> roles) {
            this.roles = roles;
        }
    
        public void addRole(Role role) {
            roles.add(role);
        }
    
        public LocalDate getCreatedTime() {
            return createdTime;
        }
    
        public void setCreatedTime(LocalDate createdTime) {
            this.createdTime = createdTime;
        }
    
        public LocalDate getModificationTime() {
            return modificationTime;
        }
    
        public void setModificationTime(LocalDate modificationTime) {
            this.modificationTime = modificationTime;
        }
    
        @PreUpdate
        public void preUpdate() {
            modificationTime = new LocalDate();
        }
    
        @PrePersist
        public void prePersist() {
            LocalDate now = new LocalDate();
            createdTime = now;
            modificationTime = now;
        }
    }
    

    我的注册控制器中的相关方法:

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String addUserFromForm(@Valid User user,
            BindingResult bindingResult, RedirectAttributes ra) {
        if (bindingResult.hasErrors()) {
            return "user/register";
        }
        userService.addUser(user);
    
        // Redirecting to avoid duplicate submission of the form
        return "redirect:/user/" + user.getUsername();
    }
    

    我的服务类:

    package com.dating.service.impl;
    
    import javax.transaction.Transactional;
    
    import org.joda.time.LocalDate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.stereotype.Service;
    
    import com.dating.domain.Role;
    import com.dating.domain.User;
    import com.dating.repository.RoleRepository;
    import com.dating.repository.UserRepository;
    import com.dating.repository.specification.UserSpecifications;
    import com.dating.service.UserService;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserRepository userRepository;
    
        @Autowired
        private RoleRepository roleRepository;
    
        @Transactional
        @Override
        public void addUser(User user) {
            user.setJoinDate(new LocalDate());
            user.setEnabled(true);
            Role role = roleRepository.findByName(Role.MEMBER);
            if (role == null) {
                role = new Role();
                role.setName(Role.MEMBER);
            }
            user.addRole(role);
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            user.setPassword(encoder.encode(user.getPassword()));
            userRepository.save(user);
            System.out.println("User Saved");
        }
    
        @Override
        public User getUserByUsername(String username) {
            return userRepository.findByUsername(username);
        }
    
        @Override
        public Iterable<User> getAllUsers() {
            return userRepository.findAll();
        }
    
        @Override
        public void updateDetails(User user) {
            userRepository.save(user);
        }
    
        @Override
        public Iterable<User> lastNameIsLike(String searchTerm) {
            return userRepository.findAll(UserSpecifications
                    .lastNameIsLike(searchTerm));
        }
    }
    

    我的NotDefaultSelect验证器:

    package com.dating.validator;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import com.dating.annotation.NotDefaultSelect;
    
    public class NotDefaultSelectValidator implements
            ConstraintValidator<NotDefaultSelect, String> {
        @Override
        public void initialize(NotDefaultSelect constraint) {
    
        }
    
        @Override
        public boolean isValid(String selectedValue, ConstraintValidatorContext ctx) {
            if (selectedValue == null) {
                return false;
            }
            if (selectedValue.equals("") || selectedValue.equals("0")
                    || selectedValue.equalsIgnoreCase("default")
                    || selectedValue.equalsIgnoreCase("please select")) {
                return false;
            }
            return true;
        }
    
    }
    

    我的uniqueUsername验证器:

    package com.dating.validator;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.dating.annotation.UniqueUsername;
    import com.dating.repository.UserRepository;
    
    public class UniqueUsernameValidator implements
            ConstraintValidator<UniqueUsername, String> {
    
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public void initialize(UniqueUsername constraint) {
    
        }
    
        @Override
        public boolean isValid(String username, ConstraintValidatorContext ctx) {
            if (username == null || userRepository.findByUsername(username) == null) {
                return true;
            }
            return false;
        }
    
    }
    

    我的UserRepository:

    package com.dating.repository;
    
    import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
    import org.springframework.data.repository.CrudRepository;
    
    import com.dating.domain.User;
    
    //Spring Data JPA Marker interfaces being extended for automatic CRUD repository creation
    public interface UserRepository extends CrudRepository<User, Long>, JpaSpecificationExecutor<User> {
    
        //Automatic query creation from method name
        public User findByUsername(String username);
    }
    

    最后是我的persistence-context.xml文件

    <!-- Data source properties -->
    <util:properties id="dataSourceSettings" location="classpath:datasource.properties" />
    
    <!-- Pooled data source using BoneCP -->
    <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
        destroy-method="close">
        <property name="driverClass" value="#{dataSourceSettings['jdbc.driverClass']}" />
        <property name="jdbcUrl" value="#{dataSourceSettings['jdbc.url']}" />
        <property name="username" value="#{dataSourceSettings['jdbc.username']}" />
        <property name="password" value="#{dataSourceSettings['jdbc.password']}" />
        <property name="idleConnectionTestPeriodInMinutes" value="60" />
        <property name="idleMaxAgeInMinutes" value="240" />
        <property name="maxConnectionsPerPartition" value="30" />
        <property name="minConnectionsPerPartition" value="10" />
        <property name="partitionCount" value="3" />
        <property name="acquireIncrement" value="5" />
        <property name="statementsCacheSize" value="100" />
        <property name="releaseHelperThreads" value="3" />
    </bean>
    
    <!-- JPA entity manager factory bean -->
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="com.dating.domain" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">#{dataSourceSettings['hibernate.dialect']}</prop>
                <prop key="hibernate.hbm2ddl.auto">#{dataSourceSettings['hibernate.hbm2ddl.auto']}
                </prop>
                <prop key="hibernate.show_sql">#{dataSourceSettings['hibernate.show_sql']}</prop>
                <prop key="hibernate.format_sql">#{dataSourceSettings['hibernate.format_sql']}</prop>
                <prop key="hibernate.use_sql_comments">#{dataSourceSettings['hibernate.use_sql_comments']}
                </prop>
            </props>
        </property>
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager" />
    
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    
    <context:annotation-config />
    
    <jpa:repositories base-package="com.dating.repository"/>
    

2 个答案:

答案 0 :(得分:7)

当您将bean发送到数据存储区时,可能第二次验证是由hibernate完成的。要将其关闭,请将其添加到persistence.xml:

<property name="javax.persistence.validation.mode" value="none"/>

https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/configuration.html说:

  

默认情况下,Bean Validation(和Hibernate Validator)已激活。创建,更新(并可选地删除)实体时,会在发送到数据库之前对其进行验证。 Hibernate生成的数据库模式也反映了在实体上声明的约束。

     

如果需要,您可以对其进行微调:

     

AUTO:如果类路径中存在Bean Validation,则激活CALLBACK和DDL。

     

CALLBACK:在创建,更新和删除时验证实体。如果不存在Bean Validation提供程序,则会在初始化时引发异常。

     

DDL :(非标准,见下文)数据库模式是在创建,更新和删除时验证实体。如果不存在Bean Validation提供程序,则会在初始化时引发异常。

     

NONE:根本不使用Bean验证

第一个显然是由Spring控制器完成的,因为@Valid注释。

答案 1 :(得分:0)

您只需将此属性添加到application.property文件中即可禁用休眠验证spring.jpa.properties.javax.persistence.validation.mode=none