冲突:使用Hibernate Validator的Spring MVC - 提交/绑定操作

时间:2014-09-09 21:40:43

标签: spring-mvc hibernate-validator spring-4 spring-mvc-initbinders

我有两个实体,它们是:人和地址,一对一的关系。关于JPA / Hibernate的源代码已被删除,以保持简单。

我的问题在于Hibernate Validator。

我对类有以下内容:

@Valid
@NotNull(message="{person.address.null}", groups={AddressCheck.class})
public Address getAddress() {
    return address;
}

地址

@NotNull(message="{field.null}", groups=AddressCheck.class)
public Person getPerson() {
    return person;
}

注意: 关于JUnit,一切正常,如果某个依赖项为null,则会创建相应的错误。

问题是当Hibernate Validator与Spring MVC 集成时,它通过绑定方法

关于视图代码,在Address类的JSP片段下面

<form:form modelAttribute="person"  method="post" >
...
<fieldset>
    <legend><spring:message code="address.form.legend"/></legend>
    <table>
        <form:hidden path="address.id"/> 
        <tr>
            <td><spring:message code="address.form.street"/></td>
            <td><form:input path="address.street" size="40"/></td>
            <td><form:errors path="address.street" cssClass="error"/></td>
        </tr>
        <tr>
            <td><spring:message code="address.form.number"/></td>
            <td><form:input path="address.number" size="40"/></td>
            <td><form:errors path="address.number" cssClass="error"/></td>
        </tr>
    </table>
</fieldset>

要创建表单视图,让JSP呈现两个对象,我有以下内容:

@RequestMapping(value="/register.htm", method=RequestMethod.GET)
public String createRegisterForm(Model model){
    logger.info("createRegisterForm GET");
    Person person = new Person();
    Address address = new Address();
    address.setId("5");//just to play/test

    person.setAddress(address);
    address.setPerson(person);

    model.addAttribute("person", person);
    return "jsp/person/registerForm";
}

观察两个setter的调用方式。

当我提交时,必须执行以下代码

@RequestMapping(value="/register.htm", method=RequestMethod.POST)
public String registerPerson( @Validated(PersonRegistrationOrdered.class) Person person, 
                              BindingResult result,
                              Model model){
    logger.info("registerPerson POST");
    logger.info("{}", person.toString());
    logger.info("{}", person.getAddress().toString());

    if(result.hasErrors()){
        logger.error("There are errors!!!!");
        model.addAttribute("person", person);

        for(ObjectError objectError : result.getAllErrors()){
            logger.error("Error {}", objectError);
        }

        return "jsp/person/registerForm";
    }
    logger.info("All fine!!!!");
    return "jsp/manolo";
}

可悲的是,控制台显示有关Address类中Person对象的错误为null,即使在 createRegisterForm 中它已被关联address.setPerson(person);

我解决此问题的独特方法是禁用验证

//@NotNull(message="{field.null}", groups=AddressCheck.class)
public Person getPerson() {
    return person;
}

第一个问题:如何在不停用@NotNull验证的情况下避免这种情况?

更重要的是,如果我想保护Address类的 id

@InitBinder
public void initBinder(WebDataBinder binder) {
    ...
    binder.setDisallowedFields("address.id");
}

从JSP文件生成的HTML代码是

<fieldset>
    <legend>Address:</legend>
    <table>
        <input id="address.id" name="address.id" type="hidden" value="5"/> 
        <tr>
            <td>Street:</td>
            <td><input id="address.street" name="address.street" type="text" value="" size="40"/></td>
            <td></td>
        </tr>
        <tr>
            <td>Number:</td>
            <td><input id="address.number" name="address.number" type="text" value="" size="40"/></td>
            <td></td>
        </tr>
    </table>
</fieldset>

但是当我提交时,我得到了

- ValidationMessages found.
- org.hibernate.validator.ValidationMessages found.
- registerPerson POST
- Person [id=5, firstName=Manuel, lastName=Jordan, alive=true, dateBirth=Wed Dec 12 00:00:00 PET 2012, dateRetirement=null, dateDeath=null, age=15, weight=34.0, salary=1500]
- Address [id=null, street=La blanquita, number=155]
- There are errors!!!!
- Error Field error in object 'person' on field 'address.id': rejected value [null]; codes [NotNull.person.address.id,NotNull.address.id,NotNull.id,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.address.id,address.id]; arguments []; default message [address.id]]; default message [The field must be not empty]

如果我正在创建一个新的Person并且使用一个特殊的类来提供一个潜在的id(例如在这种情况下为5 {例如address.setId("5");),那么对于Address的id来说,这将是一个问题。< / p>

第二个问题:我怎么能扭转这个问题?

1 个答案:

答案 0 :(得分:1)

问题是您在使用GET的方法中创建模型。这意味着只要页面被渲染,模型对象就会消失。当POST进入新的空Person时,由于自动路径扩展也会Address,但这不会正确应用关系。

有两种方法可以解决这个问题。

  1. 创建一个使用@ModelAttribute注释的方法,该方法构造了用于绑定的模型。
  2. 添加@SessionAttributes以在请求之间的会话中存储Person
  3. 要使这两个选项都有效,另一件事就是在@Validated旁边,您需要将@ModelAttribute添加到方法Person方法参数中。

    @ModelAttribute

    的方法

    首先添加创建模型对象的方法。为此,创建一个方法并使用@ModelAttribute对其进行注释,并可选择为其指定模型对象person的名称。 (这也是默认值,因为使用带有小写首字母的classname)。

    @ModelAttribute("person")
    public Person prepareModel() {
        Person person = new Person();
        Address address = new Address();
        address.setId("5");//just to play/test
    
        person.setAddress(address);
        address.setPerson(person);
        return person;
    }
    

    接下来修改您的GET和POST方法。首先,对于这两种方法,您可以删除Model作为方法参数,因为不再需要它。 Person已添加到模型中。

    @RequestMapping(value="/register.htm", method=RequestMethod.GET)
    public String createRegisterForm(){
        logger.info("createRegisterForm GET");
        return "jsp/person/registerForm";
    }
    

    对于POST添加@ModelAttribute,您不需要

    @RequestMapping(value="/register.htm", method=RequestMethod.POST)
    public String registerPerson( @Validated(PersonRegistrationOrdered.class) @ModelAttribute Person person, 
                                  BindingResult result){
        logger.info("registerPerson POST");
        logger.info("{}", person.toString());
        logger.info("{}", person.getAddress().toString());
    
        if(result.hasErrors()){
            logger.error("There are errors!!!!");
    
            for(ObjectError objectError : result.getAllErrors()){
                logger.error("Error {}", objectError);
            }
    
            return "jsp/person/registerForm";
        }
        logger.info("All fine!!!!");
        return "jsp/manolo";
    }
    

    使用@SessionAttributes

    使用@SessionAttributes,您可以在HttpSession中识别要临时存储的对象。将此批注添加到控制器类中,并在POST方法中添加SessionStatus作为参数,并删除已填充的Model

    @Controller
    @SessionAttributes(Person.class)
    public class RegisterController {
    
        @RequestMapping(value="/register.htm", method=RequestMethod.POST)
        public String registerPerson( @Validated(PersonRegistrationOrdered.class) @ModelAttribute Person person, 
                                      BindingResult result, SessionStatus status){
            logger.info("registerPerson POST");
            logger.info("{}", person.toString());
            logger.info("{}", person.getAddress().toString());
    
            if(result.hasErrors()){
                logger.error("There are errors!!!!");
    
                for(ObjectError objectError : result.getAllErrors()){
                    logger.error("Error {}", objectError);
                }
    
                return "jsp/person/registerForm";
            }
            logger.info("All fine!!!!");
            status.setComplete();
            return "jsp/manolo";
        }
    
    }
    

    您需要调用setComplete()上的SessionStatus方法,以便会话清除过时的模型对象。