Przyjrzyjmy się poniższemu prostemu kontrolerowi:
@Controller @Slf4j @RequestMapping("/person") public class PersonController { @GetMapping public ModelAndView showPersonForm() { ModelAndView modelAndView = new ModelAndView("person-form"); modelAndView.addObject("person", getPerson()); return modelAndView; } public Person getPerson() { Person person = Person.builder() .firstName("John") .lastName("Smith") .id(UUID.randomUUID().toString()) .build(); return person; } }
Metoda showPersonForm
przekazuje do widoku person-form
utworzony uprzednio obiekt person
.
Formularz widoku wygląda następująco:
<p th:text="${'Id ' + person.getId()}">35c67611-fbe9-46cf-aef3-00f8aaa3b354</p> <form th:object="${person}" th:action="@{'/person/update'}" method="post"> <div class="form-group"> <label for="firstName">Imię</label> <input type="text" class="form-control" id="firstName" th:field="*{firstName}"/> </div> <div class="form-group"> <label for="lastName">Nazwisko</label> <input type="text" class="form-control" th:classappend="${#fields.hasErrors('lastName')} ? 'is-invalid' : ''" id="lastName" th:field="*{lastName}"/> <span class="invalid-feedback" th:if="${#fields.hasErrors('lastName')}"> <ul> <li th:each="err : ${#fields.errors('lastName')}" th:text="${err}"></li> </ul> </span> </div> <button type="submit" class="btn btn-primary">Zapisz</button> </form>
Jak widać formularz umożliwia edycję pól firstName
oraz lastName
, pole id
jest tylko wyświetlane.
Metoda obsługując żądanie post wygląda następująco:
@PostMapping("/update") public String updatePerson(@Valid @ModelAttribute("person") Person person, BindingResult bindingResult) { return "person-form"; }
Jak już wspominałem, pole id
nie jest wypełniane na formularzu, nie zostanie więc przesłane wraz z obiektem person
do metody updatePerson
kontrolera.
Rozwiązaniem problemu jest użycie adnotacji @SessionAttributes
i wskazanie, które obiekty modelu mają być współdzielone w ramach sesji.
@SessionAttributes("person") public class PersonController { ... }
Obiekt person, utworzony w metodzie showPersonForm
, w momencie odesłania przez kontroler będzie uzupełniony polami znajdującymi się w formularzu. Adnotację tę można udostępnić w innym kontrolerze i uzyskać w nim dostęp do współdzielonych obiektów modelu.
Można również zdefiniować metody w kontrolerze udostępniające obiekty modelu, np:
@ModelAttribute("visitorInfo") public VisitorInfo getVisitorInfo(){ return new VisitorInfo(LocalDateTime.now()); }
gdzie klasa VisitorInfo
zdefiniowana w następująco:
@Getter @Setter @AllArgsConstructor public class VisitorInfo { private LocalDateTime lastUpdate; }
Po podaniu obiektu do sesji @SessionAttributes({"person", "visitorInfo"})
możemy zmodyfikować metodę updatePerson
tak aby aktualizowała przechowywany w obiekcie znacznik czasu.
@PostMapping("/update") public String updatePerson(@ModelAttribute("visitorInfo") VisitorInfo visitorInfo, @Valid @ModelAttribute("person") Person person, BindingResult bindingResult) { visitorInfo.setLastUpdate(LocalDateTime.now()); return "person"; }
Gdybyśmy nie dodali obiektu visitorInfo
do atrybutów sesji, to każdorazowe odwołanie poprzez @ModelAttribute("visitorInfo")
powodowałoby wywołanie metody getVisitorInfo()