Trong nhiều trường hợp khi phát triển ứng dụng web với Spring MVC, chúng ta cần nhận các tham số từ request của client. Giả sử frontend gửi cho chúng ta ba tham số id, name, age - cách đơn giản nhất là sử dụng annotation @RequestParam để nhận các giá trị này.
@GetMapping("/lay-thong-tin-nguoi-dung")
public String layThongTinNguoiDung(
// Ánh xạ giá trị tham số id từ request vào tham số id kiểu Integer
// Thuộc tính required có giá trị mặc định là true, nếu không truyền tham số này sẽ báo lỗi
// Khi required = false, nếu không truyền id thì sẽ không lỗi mà sử dụng giá trị mặc định 10086
@RequestParam(value="id",required = false,defaultValue = "10086") Integer id,
@RequestParam(value="name",required = false,defaultValue = "nguyen van a") String name,
@RequestParam(value = "age",required = false,defaultValue = "21") Integer age) {
return "id===" + id + " name===" + name + " age===" + age;
}
Cách trên nhìn có vẻ ổn, nhưng thử tưởng tượng nếu thay vì ba tham số mà là mười tham số thì sao? Lúc đó chúng ta sẽ cần khai báo mười annotation @RequestParam, điều này làm cho code trở nên khó đọc, khó bảo trì, và dễ xảy ra lỗi khi các tham số có cùng kiểu dữ liệu. Vậy có cách nào giải quyết tốt hơn không?
Có một cách tiếp cận khác là sử dụng một class entity để nhận toàn bộ mười tham số. Tuy nhiên, @RequestParam không hỗ trợ trực tiếp việc ánh xạ vào object entity. Vậy phải làm sao?
Giải pháp rất đơn giản - chúng ta có thể khai báo tham số là một instance của class entity, Spring MVC sẽ tự động ánh xạ các tham số từ request vào các thuộc tính của object đó:
@GetMapping("/lay-thong-tin-nguoi-dung")
// Ánh xạ các tham số id, name, age từ request vào entity NhanVien
public String layThongTinNguoiDung(NhanVien nhanVien) {
return nhanVien.toString();
}
Chỉ cần định nghĩa một class entity là đã giải quyết được vấn đề. Nhưng nếu muốn thực hiện xác thực tương tự như @RequestParam(required=true) thì phải làm như thế nào?
Chúng ta có thể sử dụng các annotation xác thực (validation annotations) ngay trong class entity:
@Data
public class NhanVien {
// Giá trị nhỏ nhất của id là 1
@Min(1)
// id không được để trống, nếu để trống sẽ báo lỗi
@NonNull
private Integer id;
// Đối với chuỗi, nên sử dụng @NotBlank thay vì @NonNull
// vì @NotBlank kiểm tra cả null và chuỗi rỗng
@NotBlank
private String name;
// Giá trị lớn nhất của age là 30
@Max(30)
// age không được để trống
@NonNull
private Integer age;
}
Lưu ý quan trọng: Để các annotation xác thực trong entity có hiệu lực, chúng ta phải thêm annotation @Valid vào tham số:
@GetMapping("/lay-thong-tin-nguoi-dung")
// Thêm @Valid để kích hoạt xác thực trên entity
public String layThongTinNguoiDung(@Valid NhanVien nhanVien) {
return nhanVien.toString();
}
Bằng cách này, chúng ta đã mô phỏng được chức năng của @RequestParam(required=true). Tiếp theo, nếu muốn có giá trị mặc định khi không truyền tham số (tương tự như defaultValue trong @RequestParam), chúng ta chỉ cần khởi tạo giá trị mặc định cho thuộc tính trong class entity:
@Data
public class NhanVien {
// Giá trị nhỏ nhất của id là 1
@Min(1)
// id không được để trống
@NonNull
// Nếu không truyền id, sẽ sử dụng giá trị mặc định 2
// Lưu ý: Giá trị mặc định phải thỏa mãn các ràng buộc validation (@Min, @NonNull,...)
private Integer id = 2;
// Đối với chuỗi, sử dụng @NotBlank sẽ tốt hơn
@NotBlank
private String name = "nguyen van b";
// age lớn nhất là 30
@Max(30)
// age không được để trống
@NonNull
private Integer age = 28;
}
@GetMapping("/lay-thong-tin-nguoi-dung")
public String layThongTinNguoiDung(@Valid NhanVien nhanVien) {
return nhanVien.toString();
}
Đến đây, chúng ta đã mô phỏng được đầy đủ các chức năng của @RequestParam (xác thực bắt buộc và giá trị mặc định).
Trong thực tế, đôi khi không phải tất cả tham số request đều thuộc về một entity duy nhất. Chúng ta có thể chia tham số thành nhiều entity khác nhau. Cách sử dụng hoàn toàn tương tự:
@Data
public class NhanVien {
@Min(1)
@NonNull
private Integer id = 2;
@NotBlank
private String name = "nguyen van b";
@Max(30)
@NonNull
private Integer age = 28;
}
@Data
public class SanPham {
@Min(100)
@NonNull
private Integer maSanPham = 10001;
@NotBlank
private String tenSanPham = "san pham mac dinh";
}
@GetMapping("/lay-thong-tin-nguoi-dung")
public String layThongTinNguoiDung(@Valid NhanVien nhanVien, @Valid SanPham sanPham) {
return nhanVien.toString() + "--------" + sanPham.toString();
}
Tuy nhiên, có một điểm cần lưu ý: nếu cả hai entity có cùng tên thuộc tính (ví dụ cả NhanVien và SanPham đều có thuộc tính id), khi truyền một tham số id từ client, giá trị đó sẽ được ánh xạ vào cả hai entity.