JDBC bài 13 Kỹ thuật gán tham số động (Set Parameters) hiệu quả
Ở hai bài trước, chúng ta đã hiểu được sức mạnh của PreparedStatement trong việc tối ưu hiệu năng và ngăn chặn SQL Injection bằng cách sử dụng các dấu chấm hỏi ? (placeholders). Để câu lệnh này thực sự hoạt động, bạn cần sử dụng các phương thức Setter để bơm dữ liệu thực tế vào các vị trí ? đó trước khi gọi hàm thực thi.
1. Nguyên tắc hoạt động của các hàm Setter
Giao diện PreparedStatement cung cấp hàng loạt các phương thức có tiền tố set... (ví dụ: setString, setInt, setDouble). Khi sử dụng chúng, bạn phải tuân thủ 2 nguyên tắc vàng:
- Vị trí Index bắt đầu từ 1: Tương tự như
ResultSet, vị trí của các dấu?được đánh số từ1và đếm từ trái sang phải. - Khớp kiểu dữ liệu (Strict Typing): Bạn cần chọn hàm setter tương ứng với kiểu dữ liệu của cột trong cơ sở dữ liệu. JDBC Driver sẽ tự động thực hiện việc chuyển đổi (mapping) từ kiểu dữ liệu của Java sang kiểu dữ liệu chuẩn của SQL.
2. Bảng ánh xạ kiểu dữ liệu (Java to SQL)
Dưới đây là các hàm gán tham số phổ biến nhất và cách chúng tương tác với cơ sở dữ liệu:
| Hàm Setter trong JDBC | Kiểu dữ liệu Java | Kiểu SQL tương ứng (MySQL/PostgreSQL) |
|---|---|---|
setInt(index, int x) |
int |
INT, INTEGER |
setString(index, String x) |
String |
VARCHAR, CHAR, TEXT |
setDouble(index, double x) |
double |
DOUBLE, FLOAT, NUMERIC |
setBoolean(index, boolean x) |
boolean |
BOOLEAN, TINYINT(1) |
setLong(index, long x) |
long |
BIGINT |
setObject(index, Object x) |
Object |
(Tự động nhận diện kiểu) |
Lưu ý: Đối với dữ liệu ngày tháng (Date/Time), JDBC có cách xử lý đặc biệt phức tạp hơn, chúng ta sẽ dành riêng bài 14 để mổ xẻ vấn đề này.
3. Cơn ác mộng mang tên giá trị NULL
Một trong những lỗi phổ biến nhất của lập trình viên khi làm việc với PreparedStatement là xử lý dữ liệu vắng mặt (NULL).
Giả sử bạn có một cột description (VARCHAR) cho phép để trống, và một cột discount_rate (INT) cũng cho phép để trống.
Trường hợp 1: Biến là đối tượng (Object/String)
Nếu biến Java là chuỗi, bạn truyền null trực tiếp vào setString(), JDBC có thể sẽ hiểu và đẩy giá trị NULL xuống database một cách êm đẹp.
pstmt.setString(1, null); // Chấp nhận được
Trường hợp 2: Biến là kiểu nguyên thủy (Primitive Types)
Trong Java, kiểu int hoặc double không thể chứa giá trị null (chúng có giá trị mặc định là 0 hoặc 0.0). Nếu bạn lưu mã giảm giá bằng đối tượng Integer (có thể null) nhưng lại dùng hàm setInt(), trình biên dịch sẽ báo lỗi hoặc tự động ép kiểu thành 0 (gây sai lệch nghiệp vụ).
Giải pháp chuẩn mực: Sử dụng setNull()
Để chủ động báo cho Database biết rằng vị trí này là NULL một cách an toàn và tường minh nhất, hãy dùng phương thức setNull() kết hợp với lớp hằng số java.sql.Types.
Integer discount = null; // Dữ liệu từ giao diện gửi lên bị thiếu
if (discount != null) {
pstmt.setInt(2, discount);
} else {
// Chỉ định rõ kiểu dữ liệu SQL của cột đó là gì khi truyền NULL
pstmt.setNull(2, java.sql.Types.INTEGER);
}
4. Thực hành: Cập nhật thông tin sản phẩm an toàn
Dưới đây là đoạn mã thực tế minh họa cách gán nhiều loại tham số khác nhau, bao gồm cả việc xử lý an toàn cho cột có thể chứa giá trị NULL.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
public class SetParametersExample {
private static final String URL = "jdbc:mysql://localhost:3306/shop_db";
private static final String USER = "root";
private static final String PASSWORD = "password123";
public static void main(String[] args) {
// Dữ liệu đầu vào (Ví dụ: Từ form chỉnh sửa sản phẩm)
int productId = 505;
String newName = "Bàn phím cơ Keychron";
double newPrice = 2500000.0;
Boolean isAvailable = true;
String newDescription = null; // Người dùng không nhập mô tả
// Cú pháp SQL với 4 tham số gán (?) và 1 tham số điều kiện (?)
String updateSQL = "UPDATE products SET name = ?, price = ?, is_active = ?, description = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(updateSQL)) {
// Bắt đầu bơm dữ liệu vào các dấu ? theo đúng thứ tự từ trái qua phải
pstmt.setString(1, newName);
pstmt.setDouble(2, newPrice);
pstmt.setBoolean(3, isAvailable);
// Xử lý an toàn cho trường hợp mô tả bị NULL
if (newDescription != null) {
pstmt.setString(4, newDescription);
} else {
pstmt.setNull(4, Types.VARCHAR); // Khai báo rõ đây là cột chuỗi
}
pstmt.setInt(5, productId); // Gán cho điều kiện WHERE
// Thực thi
int affectedRows = pstmt.executeUpdate();
System.out.println("Đã cập nhật thành công " + affectedRows + " sản phẩm.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5. Phương thức vạn năng: setObject()
Nếu Hiếu cảm thấy việc kiểm tra kiểu dữ liệu quá rườm rà (ví dụ như xây dựng một hàm thư viện chạy câu SQL động), PreparedStatement cung cấp một hàm tên là setObject(index, value).
Hàm này sẽ nhận bất kỳ đối tượng nào của Java và tự động nội suy ra hàm set... tương ứng dựa trên kiểu dữ liệu của biến đầu vào. Thậm chí nếu truyền null, nó cũng tự động ánh xạ.
pstmt.setObject(1, newName);
pstmt.setObject(4, newDescription);
Tuy nhiên:
setObjecthoạt động thông qua cơ chế Reflection (phản chiếu) ở tầng dưới, do đó nó sẽ chậm hơn một chút so với việc gọi trực tiếp các hàmsetStringhaysetInt. Trong các hệ thống Backend cần tối ưu hiệu năng tối đa (như hệ thống xử lý giao dịch cao mà Hiếu đang làm việc), bạn vẫn nên sử dụng các hàm Setter đặc tả rõ ràng.
All rights reserved