Mặc dù đúng là trong nhiều trường hợp, một câu lệnh SQL cơ bản sẽ hoàn thành công việc cho nhiều thay đổi hoặc truy vấn cơ sở dữ liệu, nhưng đây thường là một phương pháp hay nhất để tận dụng sự linh hoạt và lợi thế dành cho bạn bằng cách sử dụng PreparedStatements
.
Sự khác biệt cơ bản giữa câu lệnh JDBC chuẩn và PreparedStatement
được xác định rõ nhất bởi lợi ích rằng một PreparedStatement
cung cấp cho bạn và ứng dụng của bạn. Dưới đây, chúng tôi sẽ xem xét ba ưu điểm cốt lõi của PreparedStatements
qua các câu lệnh JDBC / SQL thông thường.
Ngăn chặn SQL Injection
Lợi ích đầu tiên của việc sử dụng PreparedStatement
là bạn có thể tận dụng vô số .setXYZ()
các phương thức, chẳng hạn như .setString()
, cho phép mã của bạn tự động thoát khỏi các ký tự đặc biệt, chẳng hạn như dấu ngoặc kép trong câu lệnh SQL được truyền vào, ngăn chặn SQL injection
luôn nguy hiểm tấn công.
Ví dụ:trong một câu lệnh SQL tiêu chuẩn, có thể điển hình là chèn các giá trị nội dòng trực tiếp với câu lệnh, như sau:
statement = "INSERT INTO books (title, primary_author, published_date) VALUES ('" + book.getTitle() + "', '" + book.getPrimaryAuthor() + "', '" + new Timestamp(book.getPublishedDate().getTime()) + "'";
Điều này sẽ buộc bạn phải thực thi mã của riêng mình để ngăn chặn việc đưa vào SQL bằng cách thoát dấu ngoặc kép và các ký tự đặc biệt khác khỏi các giá trị được chèn.
Ngược lại, một PreparedStatement
có thể được gọi như sau, sử dụng .setXYZ()
phương pháp chèn giá trị với ký tự tự động thoát trong khi thực thi phương thức:
ps = connection.prepareStatement("INSERT INTO books (title, primary_author, published_date) VALUES (?, ?, ?)");
ps.setString(1, book.getTitle());
ps.setString(2, book.getPrimaryAuthor());
ps.setTimestamp(3, new Timestamp(book.getPublishedDate().getTime()));
ps.executeUpdate();
Pre-Compilation
Một lợi ích khác của PreparedStatement
là bản thân SQL đã được pre-compiled
một lần duy nhất và sau đó được hệ thống giữ lại trong bộ nhớ, thay vì được biên dịch mỗi lần câu lệnh được gọi. Điều này cho phép thực thi nhanh hơn, đặc biệt khi PreparedStatement
được sử dụng cùng với batches
, cho phép bạn thực thi một chuỗi (hoặc batches
) của các câu lệnh SQL cùng một lúc trong một kết nối cơ sở dữ liệu.
Ví dụ, ở đây chúng tôi có một hàm chấp nhận một List
của những cuốn sách. Đối với mỗi book
trong danh sách, chúng tôi muốn thực thi một INSERT
nhưng chúng tôi sẽ thêm tất cả chúng vào một loạt PreparedStatements
và thực hiện tất cả trong một cú ngã:
public void createBooks(List<Entity> books) throws SQLException {
try (
Connection connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement("INSERT INTO books (title, primary_author, published_date) VALUES (?, ?, ?)");
) {
for (Entity book : books) {
ps.setString(1, book.getTitle());
ps.setString(2, book.getPrimaryAuthor());
ps.setTimestamp(3, new Timestamp(book.getPublishedDate().getTime()));
ps.addBatch();
}
ps.executeBatch();
}
}
Chèn các kiểu dữ liệu bất thường trong câu lệnh SQL
Ưu điểm cuối cùng của PreparedStatements
mà chúng tôi sẽ đề cập đến là khả năng chèn các kiểu dữ liệu bất thường vào chính câu lệnh SQL, chẳng hạn như Timestamp
, InputStream
và nhiều hơn nữa.
Ví dụ:chúng ta có thể sử dụng PreparedStatement
để thêm ảnh bìa vào hồ sơ sách của chúng tôi bằng cách sử dụng .setBinaryStream()
phương pháp:
ps = connection.prepareStatement("INSERT INTO books (cover_photo) VALUES (?)");
ps.setBinaryStream(1, book.getPhoto());
ps.executeUpdate();