99 Ngày Spring — Ngày 12: Request mapping & tham số — @PathVariable, @RequestParam
Ngày 11, API của ta trả đúng một danh sách cứng — gọi kiểu gì cũng ra bấy nhiêu. API thật phải nghe được ý người gọi: "cho tôi bài viết số 42", "tìm bài chứa spring, trang 2". Hôm nay ta học hai kênh nhận ý đó — @PathVariable trong đường dẫn và @RequestParam sau dấu hỏi — cùng chuẩn mực thiết kế URL mà người phỏng vấn rất hay soi.

@PathVariable — biến trong đường dẫn
Muốn /api/posts/42 thì đường dẫn phải có chỗ trống: khai {id} trong mapping rồi hứng bằng @PathVariable. Tiện thể, gom prefix chung của cả controller lên @RequestMapping cấp class:
@RestController@RequestMapping("/api/posts") // prefix CHUNG cho cả classpublic class PostController {
@GetMapping // GET /api/posts
public List<Post> list() { ... }
@GetMapping("/{id}") // GET /api/posts/42
public Post detail(@PathVariable Long id) {
return postService.find(id); // "42" → Long 42 — Spring tự đổi kiểu
}
@GetMapping("/{id}/comments") // GET /api/posts/42/comments
public List<Comment> comments(@PathVariable Long id) { ... }
}
// Đường dẫn KHÔNG khớp kiểu? GET /api/posts/abc → 400 Bad Request luôn// (Spring không đổi được "abc" thành Long — chặn từ cửa)Tên
{id}khớp tên tham số là đủ — khác tên thì chỉ định@PathVariable("id").Khai
Long idthay vìString: Spring tự đổi kiểu, và/abcbị chặn 400 trước khi vào method — validation miễn phí tầng đầu.Prefix cấp class là thói quen sạch: đổi
/api/poststhành/api/v2/postschỉ sửa một dòng.
@RequestParam — tham số sau dấu hỏi
Kênh thứ hai: query string — phần ?keyword=java&page=2 — dành cho lọc, phân trang, sắp xếp:
// GET /api/posts?keyword=java&page=2&size=10@GetMappingpublic List<Post> search(
@RequestParam String keyword, // BẮT BUỘC — thiếu là 400
@RequestParam(defaultValue = "0") int page, // tùy chọn + mặc định
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String author) { // tùy chọn, có thể null
return postService.search(keyword, author, page, size);
}
// ?tag=java&tag=spring → gom vào danh sách:@GetMapping("/by-tags")public List<Post> byTags(@RequestParam List<String> tag) { ... }
// Mẹo: khai kiểu đúng (int, boolean, LocalDate...) — Spring convert giúp,// sai định dạng thì 400 ngay, khỏi tự parse chuỗi trong controllerBa mức linh hoạt: bắt buộc (mặc định — thiếu là 400),
defaultValue(vắng thì điền sẵn),required = false(vắng thìnull— nhớ Ngày 09 mà phòng thân).Bộ ba
keyword/page/sizechính là hình hài phân trang chuẩn — Ngày 18 nâng cấp lênPageablecủa Spring Data.Đừng nhận
Stringrồi tựparseInt— khai đúng kiểu để Spring convert và trả lỗi chuẩn giúp bạn.
Chọn kênh nào? Định danh vs bộ lọc
Câu hỏi kinh điển: khi nào đường dẫn, khi nào query? Quy tắc một câu: định danh tài nguyên vào path, tùy chọn vào query:
// PATH VARIABLE — định danh tài nguyên: "CON NÀO?"
GET /api/posts/42 // bài viết số 42 — thiếu id thì URL vô nghĩa
GET /api/users/nam/posts // bài của user nam
// REQUEST PARAM — bộ lọc/tùy chọn: "LỌC/SẮP XẾP THẾ NÀO?"
GET /api/posts?page=2&size=10&sort=views // bỏ hết vẫn có nghĩa
GET /api/posts?keyword=spring&author=nam
// Đặt cạnh nhau cho quen mắt:
GET /api/posts/42/comments?page=0&size=20
// └─ định danh ─┘└──── tùy chọn ────┘
// Quy ước URL tử tế:
// ✓ danh từ SỐ NHIỀU: /posts, /users — KHÔNG động từ: /getPosts ✗
// ✓ phân cấp theo quan hệ: /posts/42/comments
// ✓ kebab-case: /api/reading-lists — không camelCase trong URLTự kiểm: bỏ hết query mà URL vẫn trỏ đúng "thứ đó" → thiết kế ổn; còn
/api/posts/page/2là mùi thiết kế — trang 2 không phải một tài nguyên.URL là hợp đồng công khai của API — đổi sau này là phá client; đầu tư đặt tên từ hôm nay rẻ hơn nhiều so với đổi về sau.
Quy tắc mapping cần nhớ
// Một method — một cặp (động từ + đường dẫn). Trùng nhau là Boot từ chối chạy:// Ambiguous mapping. Cannot map 'postController' method ... — lỗi lúc KHỞI ĐỘNG, may!
@GetMapping("/{id}") // GET /api/posts/42 → đọc@PostMapping // POST /api/posts → tạo (Ngày 13!)@PutMapping("/{id}") // PUT /api/posts/42 → sửa@DeleteMapping("/{id}") // DELETE /api/posts/42 → xóa
// Cùng đường dẫn, KHÁC động từ → hợp lệ, và chính là bộ mặt REST chuẩn// Path cụ thể thắng path có biến: /posts/latest được thử TRƯỚC /posts/{id}Trùng (động từ + path) là sập lúc khởi động — lỗi ồn ào là lỗi rẻ; đỡ hơn nhiều so với lặng lẽ gọi nhầm method.
Cùng path khác động từ là chuẩn REST:
GET /posts/42đọc,DELETE /posts/42xóa — Ngày 13–15 lấp nốtPOST/PUTvới body và status code.Boot còn tự xử lý tinh tế:
/posts/latestcụ thể hơn nên được ưu tiên trước/posts/{id}— cứ khai, không cần lo thứ tự method.
Bài tập nhỏ
Thêm
GET /api/books/{id}vàoBookControllerhôm qua — thử/api/books/abcvà quan sát 400.Viết
GET /api/books?minPrice=&maxPrice=lọc theo khoảng giá — dùngdefaultValuecho hai đầu.Thiết kế URL cho: "các bình luận của bài 42, trang 2, sắp theo mới nhất" — viết mapping đầy đủ.
Cố ý khai hai
@GetMapping("/api/books")— đọc kỹ lỗiAmbiguous mappingđể nhận diện sau này.
Kết luận
API đã biết lắng nghe: @PathVariable hứng định danh trong đường dẫn, @RequestParam hứng bộ lọc sau dấu hỏi, Spring convert kiểu và chặn dữ liệu xấu từ cửa, còn URL theo chuẩn danh-từ-số-nhiều phân cấp là bộ mặt chuyên nghiệp của service. Nhưng GET chỉ đọc — muốn tạo mới tài nguyên, dữ liệu phải đi trong request body. Ngày 13: @RequestBody, DTO, và vì sao đừng bao giờ phơi entity ra ngoài. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


