Trong quá trình xử lý điều kiện, đoạn mã sau có thể gây ra lỗi trỏ tới null (NullPointerException):
.ge(StringUtils.isNotBlank(param.getCreateTimeStart()),
CommonFileInfo::getCreatetime,
dtf.parse(param.getCreateTimeStart()));
Vấn đề chính nằm ở thứ tự đánh giá các tham số trong Java.
Phân tích vấn đề
Đoạn mã trên tương tự cách sử dụng chuỗi gọi liên tiếp của công cụ xây dựng điều kiện trong MyBatis-Plus, ví dụ như QueryWrapper:
.ge(
StringUtils.isNotBlank(param.getCreateTimeStart()),
CommonFileInfo::getCreatetime,
dtf.parse(param.getCreateTimeStart())
);
Bạn kỳ vọng rằng:
- Nếu
param.getCreateTimeStart()lànullhoặc rỗng,StringUtils.isNotBlank(...)sẽ trả vềfalse. - Khi đó
.ge(false, ..., ...)sẽ không thêm điều kiện này vào vàdtf.parse(...)sẽ không được thực thi.
Tuy nhiên, trên thực tế: Trước khi gọi phương thức .ge(...), Java đã tính toán trước tất cả các giá trị của các tham số, bao gồm cả tham số thứ ba dtf.parse(param.getCreateTimeStart()).
Do đó, dù điều kiện đầu tiên là false, dtf.parse(param.getCreateTimeStart()) vẫn được thực thi. Nếu param.getCreateTimeStart() là null, dtf.parse(null) sẽ ném ra ngoại lệ NullPointerException (hoặc DateTimeParseException, tùy thuộc vào kiểu của dtf).
Điểm mấu chốt: Java là ngôn ngữ đánh giá nghiêm ngặt (eager evaluation), nghĩa là tất cả các tham số thực tế đều được tính toán xong trước khi phương thức được gọi.
Cách giải quyết đúng
Bạn cần tránh gọi trực tiếp các phương thức có khả năng ném ngoại lệ bên trong danh sách tham số, mà thay vào đó hãy đặt logic phân tích bên trong khối điều kiện. Ví dụ:
Giải pháp 1: Kiểm tra và phân tích trước
String createTimeStart = param.getCreateTimeStart();
if (StringUtils.isNotBlank(createTimeStart)) {
try {
Date parsedDate = dtf.parse(createTimeStart);
queryWrapper.ge(CommonFileInfo::getCreatetime, parsedDate);
} catch (Exception e) {
// Xử lý ngoại lệ phân tích, ví dụ ghi log hoặc ném ngoại lệ nghiệp vụ
throw new IllegalArgumentException("Định dạng thời gian tạo không hợp lệ", e);
}
}
Giải pháp 2: Sử dụng toán tử ba ngôi kèm hàm phân tích an toàn (không khuyến khích cho logic phức tạp)
Nếu dtf.parse có thể xử lý null một cách an toàn (thường thì không), hoặc bạn có thể đóng gói nó thành một hàm an toàn:
Date safeParse(String str) {
if (StringUtils.isBlank(str)) return null;
try {
return dtf.parse(str);
} catch (Exception e) {
return null; // Hoặc ném ngoại lệ
}
}
// Khi sử dụng:
String start = param.getCreateTimeStart();
queryWrapper.ge(
StringUtils.isNotBlank(start),
CommonFileInfo::getCreatetime,
safeParse(start)
);
Lưu ý: Mặc dù vậy, safeParse(start) vẫn sẽ được thực thi trước khi .ge() được gọi, chỉ là nó có kiểm tra null bên trong. Điều này giúp tránh ngoại lệ nhưng logic không rõ ràng bằng giải pháp 1.
Thêm thông tin: Ý định thiết kế của .ge(condition, ...) trong MyBatis-Plus
Các phương thức điều kiện của MyBatis-Plus (như .eq, .ge) hỗ trợ tham số condition để kiểm soát việc có thêm điều kiện hay không, nhưng các tham số khác phải đảm bảo có thể được đánh giá an toàn. Nó không phải là "đánh giá lười" (lazy evaluation), tức là nó sẽ không bỏ qua việc tính toán các tham số khác khi condition=false.
Do đó, không nên đưa các biểu thức có khả năng ném ngoại lệ trực tiếp vào danh sách tham số.
Kết luận
- Nhận thức sai lầm: Nghĩ rằng khi
condition=false, các tham số phía sau sẽ không được tính toán. - Nhận thức đúng: Tất cả các tham số đều được tính toán trước khi phương thức được gọi, bất kể giá trị của
conditionlà gì. - Giải pháp: Kiểm tra xem chuỗi có rỗng hay không trước khi phân tích và cuối cùng mới gọi
.ge().