Spring mvc SpringMVC:验证、后重定向获取、部分更新、乐观并发、字段安全

Spring mvc SpringMVC:验证、后重定向获取、部分更新、乐观并发、字段安全,spring-mvc,optimistic-locking,post-redirect-get,optimistic-concurrency,Spring Mvc,Optimistic Locking,Post Redirect Get,Optimistic Concurrency,[这是我看到的关于Spring MVC的常见问题列表,它们以类似的方式解决。我在这里发布了它们,因此我可以很容易地从其他问题中参考它们] 如何使用表单仅更新模型实体的几个字段 如何在SpringMVC中使用该模式,尤其是在表单验证中 如何保护实体中的某些字段 如何实现 要部分更新实体,应使用@SessionAttributes在请求之间的会话中存储模型。您可以使用隐藏的表单字段,但会话更安全 要使用p/R/G进行验证,请使用flashAttributes 要保护字段,请使用webDataBind

[这是我看到的关于Spring MVC的常见问题列表,它们以类似的方式解决。我在这里发布了它们,因此我可以很容易地从其他问题中参考它们]

如何使用表单仅更新模型实体的几个字段

如何在SpringMVC中使用该模式,尤其是在表单验证中

如何保护实体中的某些字段

如何实现

  • 要部分更新实体,应使用
    @SessionAttributes
    在请求之间的会话中存储模型。您可以使用隐藏的表单字段,但会话更安全

  • 要使用p/R/G进行验证,请使用
    flashAttributes

  • 要保护字段,请使用
    webDataBinder.setAllowedFields(“field1”、“field2”和…)
    或创建特定于表单的类,然后将值复制到实体。实体不需要id和版本设置器(如果使用Hibernate)

  • 要使用乐观并发控制,请在实体中使用
    @Version
    注释,并在控制器上使用
    @SessionAttributes

  • 示例代码:

    @Controller
    @RequestMapping("/foo/edit/{id}")
    @SessionAttributes({FooEditController.ATTRIBUTE_NAME})
    public class FooEditController {
    
        static final String ATTRIBUTE_NAME = "foo";
        static final String BINDING_RESULT_NAME = "org.springframework.validation.BindingResult." + ATTRIBUTE_NAME;
    
        @Autowired
        private FooRepository fooRepository;
    
        /*
         Without this, user can set any Foo fields they want with a custom HTTP POST
         setAllowedFields disallows all other fields. 
         You don't even need setters for id and version, as Hibernate sets them using reflection
        */
        @InitBinder
        void allowFields(WebDataBinder webDataBinder){
            webDataBinder.setAllowedFields("name"); 
        }
    
        /*
         Get the edit form, or get the edit form with validation errors
        */
        @RequestMapping(method = RequestMethod.GET)
        String getForm(@PathVariable("id") long id, Model model) {
    
            /* if "fresh" GET (ie, not redirect w validation errors): */
            if(!model.containsAttribute(BINDING_RESULT_NAME)) {
                Foo foo = fooRepository.findOne(id);
                if(foo == null) throw new ResourceNotFoundException();
                model.addAttribute(ATTRIBUTE_NAME, foo);
            }
    
            return "foo/edit-form";
        }
    
        /*
         @Validated is better than @Valid as it can handle http://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-groups.html
         @ModelAttribute will load Foo from session but also set values from the form post
         BindingResult contains validation errors
         RedirectAttribute.addFlashAttribute() lets you put stuff in session for ONE request
         SessionStatus lets you clear your SessionAttributes
        */
        @RequestMapping(method = RequestMethod.POST)
        String saveForm(
           @Validated @ModelAttribute(ATTRIBUTE_NAME) Foo foo,
           BindingResult bindingResult, 
           RedirectAttributes redirectAttributes, 
           HttpServletRequest request, 
           SessionStatus sessionStatus
        ) {
    
            if(!bindingResult.hasErrors()) {
                try {
                    fooRepository.save(foo);
                } catch (JpaOptimisticLockingFailureException exp){
                    bindingResult.reject("", "This record was modified by another user. Try refreshing the page.");
                }
            }
    
            if(bindingResult.hasErrors()) {
    
                //put the validation errors in Flash session and redirect to self
                redirectAttributes.addFlashAttribute(BINDING_RESULT_NAME, bindingResult);
                return "redirect:" + request.getRequestURI();
            }
    
            sessionStatus.setComplete(); //remove Foo from session
    
            redirectAttributes.addFlashAttribute("message", "Success. The record was saved");
            return "redirect:" + request.getRequestURI();
        }
    }
    
    Foo.java:

    @Entity
    public class Foo {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Version //for optimistic concurrency control
        private int version;
    
        @NotBlank
        private String name;
    
        public Long getId() {
            return id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    
    edit-form.jsp(与Twitter引导兼容):


    谢谢你的回答,不过我有几个问题。第一个问题就是我的电子邮件地址会被验证,所以如果我没有输入一个有效的电子邮件地址,它会显示一个小消息,否则它不会,我甚至可以向服务器提交一个空白表单。我还应该像我那样显示成功提交的消息吗?@Jack使用Hibernate Validator的
    @NotBlank
    @Email
    验证。对于成功消息,设置一个名为“message”或类似的flash属性,然后在JSP中重定向后显示该消息,确保正确设置了Hibernate验证程序。在maven中:
    org.hibernate hibernate validator 5.1.2.Final
    ;但是,不显示错误消息。它现在可以工作了。非常感谢,我可以知道如何自定义消息吗?另外,如何在可选字段上设置限制?在其中一个字段上,我有@Digits(分数=0,整数=14)@Size(最小=10,最大=14),如果我没有输入任何值,它会显示大小必须在7到14之间,数值超出边界(.expected),但是该字段是可选的,只有在输入时才应用限制。请也看看这个问题
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
    <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
    
    <form:form modelAttribute="foo">
    
        <spring:hasBindErrors name="foo">
            <c:if test="${errors.globalErrorCount > 0}">
                <div class="alert alert-danger" role="alert"><form:errors/></div>
            </c:if>
        </spring:hasBindErrors>
    
        <c:if test="${not empty message}">
          <div class="alert alert-success"><c:out value="${message}"/></div>
        </c:if>
    
        <div class="panel panel-default">
            <div class="panel-heading">
                <button class="btn btn-primary" name="btnSave">Save</button>
            </div>
    
            <div class="panel-body">
    
                <spring:bind path="name">
                    <div class="form-group${status.error?' has-error':''}">
                        <form:label path="name" class="control-label">Name <form:errors path="name"/></form:label>
                        <form:input path="name" class="form-control" />
                    </div>
                </spring:bind>
    
            </div>
        </div>
    
    </form:form>
    
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public class ResourceNotFoundException extends RuntimeException {
    }