การเก็บรักษาเงื่อนไขการค้นหาข้ามหน้าด้วย @SessionAttributes ของ Spring MVC
บทนำ
เมื่อสร้าง admin panel คุณจะเจอสถานการณ์นี้:
- กรองตาม category “Action” ในรายการภาพยนตร์
- ไปที่หน้าแก้ไขภาพยนตร์เฉพาะ
- บันทึกและกลับมาที่รายการ… ตัวกรองถูก reset
นี่คือเรื่องราวที่เราแก้ปัญหา “ตัวกรองหายไปหลังกลับมา” ด้วย @SessionAttributes
โครงสร้างของปัญหา
Spring MVC controllers สร้าง instance ใหม่ต่อ request
แม้จะใส่ค่า form input ใน Model พวกมันก็หายไปใน request ถัดไป
@Controller
@RequestMapping("/admin/films")
public class FilmController {
@GetMapping
public String list(@ModelAttribute FilmSearchForm form, Model model) {
// form เป็น instance ใหม่ทุกครั้ง → เงื่อนไขการค้นหาหาย
List<Film> films = filmService.search(form);
model.addAttribute("films", films);
return "admin/films/list";
}
}
วิธีใช้ @SessionAttributes
การเพิ่ม @SessionAttributes ที่ controller class จะบันทึก objects ที่ระบุใน session
@Controller
@RequestMapping("/admin/films")
@SessionAttributes("filmSearchForm") // ← เพิ่มนี้
public class FilmController {
@ModelAttribute("filmSearchForm")
public FilmSearchForm initSearchForm() {
return new FilmSearchForm(); // ค่าเริ่มต้นเมื่อไม่อยู่ใน session
}
@GetMapping
public String list(
@ModelAttribute("filmSearchForm") FilmSearchForm form,
Model model) {
List<Film> films = filmService.search(form);
model.addAttribute("films", films);
return "admin/films/list";
}
@PostMapping("/search")
public String search(
@ModelAttribute("filmSearchForm") FilmSearchForm form,
BindingResult result) {
if (result.hasErrors()) {
return "admin/films/list";
}
// Redirect ไปยัง GET หลัง POST (PRG pattern)
return "redirect:/admin/films";
}
}
คำอธิบาย Flow
- การเข้าครั้งแรก: method
@ModelAttributeสร้างFilmSearchFormและบันทึกไว้ใน session - ส่ง search form: ค่า
formถูกอัปเดตใน session - ไปหน้าอื่นและกลับมา:
formถูก restore จาก session → เงื่อนไขการค้นหายังคงอยู่
การล้าง Session
เพื่อ implement ปุ่ม “reset เงื่อนไขการค้นหา” ให้ลบค่า session
@PostMapping("/search/reset")
public String resetSearch(SessionStatus sessionStatus) {
sessionStatus.setComplete(); // ลบ session attributes
return "redirect:/admin/films";
}
การเรียก SessionStatus#setComplete() จะล้าง session attributes ทั้งหมดที่ controller นี้จัดการ
Template Thymeleaf
<!-- list.html -->
<form method="post" action="/admin/films/search">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<input type="text" name="keyword"
th:value="${filmSearchForm.keyword}"
placeholder="ค้นหาชื่อ">
<select name="categoryId">
<option value="">ทุก Category</option>
<option th:each="cat : ${categories}"
th:value="${cat.categoryId}"
th:text="${cat.name}"
th:selected="${cat.categoryId == filmSearchForm.categoryId}">
</option>
</select>
<button type="submit">ค้นหา</button>
<button type="submit" formaction="/admin/films/search/reset">Reset</button>
</form>
th:value="${filmSearchForm.keyword}" แสดงค่าที่ restore จาก session ใน form
หมายเหตุ
① Sessions แยกตาม Controller
@SessionAttributes เป็น per controller
filmSearchForm ที่บันทึกใน FilmController ไม่สามารถมองเห็นได้จาก ActorController
② ปัญหา Session สะสม
ถ้าเปิดหลาย tab หรือ operate นานโดยไม่ปิด browser objects จำนวนมากอาจสะสมใน session
รวม timing สำหรับการเรียก SessionStatus#setComplete() ไว้ใน design ของคุณ
③ ความขัดแย้งใน Multi-Tab
ถ้าเปิดสอง tab ในบราวเซอร์เดียวกันและค้นหาด้วยเงื่อนไขต่างกันในแต่ละ tab พวกมัน share session ซึ่งอาจทำให้เกิดพฤติกรรมที่ไม่คาดคิด
นี่ไม่น่าจะเป็นปัญหาสำหรับกรณีใช้งาน admin panel แต่ควรระวัง
เปรียบเทียบกับ localStorage (กรณี Vue 3)
ในแอพฝั่งลูกค้า (Vue 3) เราเก็บสถานะตัวกรองด้วย localStorage
| วิธีการเก็บ | สถานการณ์ที่เหมาะ |
|---|---|
@SessionAttributes | Spring MVC + Thymeleaf, การจัดการ state ฝั่ง server |
localStorage | SPA (Vue/React), การจัดการ state ฝั่ง client |
Pinia (Vue 3) | การจัดการ global state ใน SPA (หายเมื่อ reload หน้า) |
Pinia + localStorage | SPA ที่ต้องการเก็บ state หลัง reload หน้า |
สรุป
@SessionAttributes เป็นวิธีมาตรฐานสำหรับการเก็บเงื่อนไขการค้นหาและค่า input ข้าม page transition ใน Spring MVC + Thymeleaf
@SessionAttributes("formName")— ประกาศสิ่งที่จะบันทึกใน session ที่ controller@ModelAttribute("formName")— สร้างค่าเริ่มต้นเมื่อไม่อยู่ใน sessionSessionStatus#setComplete()— การ reset เพื่อล้าง session
ถ้า implement แบบง่ายๆ ปัญหา “หายไปหลังกลับมา” เกิดขึ้นง่าย — การสร้างมันเข้าไปใน design ตั้งแต่แรกช่วยให้ไม่มีปัญหาในภายหลัง
แผนที่บทความของ Series นี้
→ การสร้างแอพ DVD Rental สำหรับผู้ใช้ควบคู่กับหน้าจัดการ — ภาพรวมโครงสร้าง Vue 3 + Spring Boot