Spring MVC: validación, publicación posterior a la redirección, actualizaciones parciales, concurrencia optimista, seguridad de campo

[Esta es una lista de preguntas comunes que veo sobre Spring MVC, que se resuelven de manera similar. Los publiqué aquí, así que puedo referirme a ellos fácilmente de otras preguntas]

¿Cómo actualizo solo unos pocos campos de una entidad modelo con formularios?

¿Cómo uso el patrón Post-Redirect-Get con Spring MVC, especialmente con la validación de formulario?

¿Cómo puedo asegurar ciertos campos en mis entidades?

¿Cómo implemento Optimistic Concurrency Control ?

  1. Para actualizar parcialmente una entidad, debe usar @SessionAttributes para almacenar el modelo en sesión entre las solicitudes. Puede usar campos de formulario ocultos, pero la sesión es más segura.

  2. Para usar P / R / G con validación, use flashAttributes

  3. Para proteger los campos use webDataBinder.setAllowedFields("field1","field2",...) o cree una clase específica para el formulario y luego copie los valores a su entidad. Las entidades no requieren setters para id y versión (si usan Hibernate).

  4. Para usar Control de Concurrencia Optimista, use la anotación @Version en su Entidad y use @SessionAttributes en su controlador.

Código de ejemplo:

 @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 (compatible con Twitter Bootstrap):

 < %@ 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" %>        
Name

ResourceNotFoundException.java:

 @ResponseStatus(HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { }