Dynamic SQL (SQL động)¶
Các thẻ <if>, <choose>, <where>, <set> và <trim> được hỗ trợ đầy đủ. Thay vì xây dựng và duyệt cây AST lúc runtime như MyBatis, LarkBatis phẳng hoá toàn bộ cấu trúc thẻ XML thành các biến điều kiện cục bộ (condition locals) và các lệnh nối chuỗi StringBuilder cố định.
Cơ chế biên dịch mã nguồn¶
<select id="search" resultType="com.example.app.User">
SELECT id, name, email, created_at FROM users
<where>
<if test="name != null">AND name LIKE #{name}</if>
<if test="minAge != null">AND age >= #{minAge}</if>
</where>
ORDER BY id
</select>
Mã nguồn Java sinh ra tương ứng:
@Override
public List<User> search(UserQuery q) {
boolean c0 = q.getName() != null; // (1)!
boolean c1 = q.getMinAge() != null;
StringBuilder sb = new StringBuilder(96); // (2)!
sb.append("SELECT id, name, email, created_at FROM users");
if (c0 | c1) {
sb.append(" WHERE"); // (3)!
}
if (c0) {
sb.append(" name LIKE ?");
}
if (c1) {
sb.append(c0 ? " AND age >= ?" : " age >= ?"); // (4)!
}
sb.append(" ORDER BY id");
String sql = sb.toString();
Connection c = s.conn();
try (PreparedStatement ps = c.prepareStatement(sql)) {
int i = 1;
if (c0) {
ps.setString(i++, q.getName()); // (5)!
}
if (c1) {
JdbcCodec.setInt(ps, i++, q.getMinAge());
}
// ... đọc ResultSet
}
}
- Mỗi biểu thức
testđược đánh giá đúng một lần vào biến boolean cục bộ. Biến này dùng chung cho cả khâu nối chuỗi SQL lẫn khâu bind tham số JDBC. - Kích thước
StringBuilderđược tính toán trước từ lúc build. - Thẻ
<where>được biên dịch thành lệnhifthông thường, không quét chuỗi để xóaAND/ORlúc runtime. - Tiền tố
AND/ORđược tối ưu thành toán tử ba ngôi (ternary operator) dựa trên các biến boolean. - Khâu gán tham số
PreparedStatementđi qua cùng thứ tự điều kiện, không cần bảng tra cứu động.
<if>¶
Nối đoạn SQL khi điều kiện đúng. Hỗ trợ lồng nhau và đặt bên trong thẻ <foreach>.
<choose>, <when>, <otherwise>¶
Tương đương câu lệnh switch / if-else:
<choose>
<when test="status != null">AND status = #{status}</when>
<otherwise>AND status = 'NEW'</otherwise>
</choose>
<where> và <set>¶
<where> chỉ chèn từ khóa WHERE nếu có ít nhất một điều kiện con thỏa mãn, và tự động bỏ từ khóa AND/OR ở nhánh đầu tiên. <set> hoạt động tương tự với từ khóa SET và dấu phẩy ở cuối câu:
<update id="rename">
UPDATE users
<set>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
if (c0 | c1) sb.append(" SET");
if (c0) sb.append(c1 ? " name = ?," : " name = ?");
if (c1) sb.append(" email = ?");
sb.append(" WHERE id = ?");
<trim>¶
Hỗ trợ các thuộc tính hằng số (prefix, suffix, prefixOverrides, suffixOverrides) và được tối ưu hóa lúc compile.
Ngữ pháp biểu thức test¶
Khác với MyBatis (dùng OGNL runtime), LarkBatis sử dụng ngữ pháp kiểm tra kiểu tĩnh:
| Biểu thức hỗ trợ | Ví dụ |
|---|---|
| Kiểm tra null | name != null, probe.email == null |
| So sánh giá trị | age >= 18, status == 'NEW', id != other.id |
| Toán tử logic | and, or, not, dấu ngoặc () |
| Kiểm tra độ dài / collection | ids.size() > 0, name.length() > 3, !ids.isEmpty() |
| Phương thức trả về boolean | user.isActive() |
| Biến boolean | active (với active có kiểu boolean hoặc Boolean) |
Mọi biểu thức nằm ngoài danh sách trên sẽ báo lỗi biên dịch.
Không hỗ trợ truthiness kiểu OGNL
Trong LarkBatis, bạn phải viết rõ ràng: count != 0, user != null, hoặc !list.isEmpty().
Bảng so sánh xử lý giá trị null¶
| Biểu thức | LarkBatis | MyBatis / OGNL |
|---|---|---|
a == null / a != null |
Lan truyền null an toàn | Như nhau |
age <= 18 khi age là null |
false |
true (OGNL tự ép null thành 0) |
user.isActive() khi user là null |
false |
Ném ngoại lệ NullPointerException |
Quản lý Statement Cache cho Dynamic SQL¶
Mỗi tổ hợp điều kiện tạo ra một chuỗi SQL riêng biệt. Với n thẻ <if> độc lập, số lượng biến thể tối đa là 2ⁿ chuỗi. LarkBatis tự động theo dõi số lượng biến thể qua LarkBatisSql.trackVariants() để ngăn chặn tràn bộ nhớ cache. Xem Raw SQL & An toàn.