Sự khác biệt giữa Statement và PreparedStatement

Java hàng đầu

Tôi vừa công bố khóa học Learn Spring mới , tập trung vào các nguyên tắc cơ bản của Spring 5 và Spring Boot 2:

>> KIỂM TRA KHÓA HỌC

1. Khái quát chung

Trong hướng dẫn này, chúng ta sẽ khám phá sự khác biệt giữa giao diện StatementPreparedStatement của JDBC . Chúng tôi sẽ không đề cập đến CallableStatement , một giao diện API JDBC được sử dụng để thực thi các thủ tục được lưu trữ.

2. Giao diện API JDBC

Cả StatementPreparedStatement đều có thể được sử dụng để thực thi các truy vấn SQL. Các giao diện này trông rất giống nhau. Tuy nhiên, chúng khác nhau đáng kể về các tính năng và hiệu suất:

  • Câu lệnh - Được sử dụng để thực thi các truy vấn SQL dựa trên chuỗi
  • PreparedStatement - Được sử dụng để thực thi các truy vấn SQL được tham số hóa

Để có thể sử dụng StatementPreparedStatement trong các ví dụ của chúng tôi, chúng tôi sẽ khai báo trình kết nối JDBC h2 như một phần phụ thuộc trong tệp pom.xml của chúng tôi :

 com.h2database h2 1.4.200 

Hãy xác định một thực thể mà chúng ta sẽ sử dụng trong suốt bài viết này:

public class PersonEntity { private int id; private String name; // standard setters and getters }

3. Tuyên bố

Thứ nhất, giao diện Statement chấp nhận các chuỗi dưới dạng truy vấn SQL. Do đó, mã trở nên ít đọc hơn khi chúng ta nối các chuỗi SQL:

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES(" + personEntity.getId() + ", '" + personEntity.getName() + "')"; Statement statement = connection.createStatement(); statement.executeUpdate(query); }

Thứ hai, nó dễ bị SQL injection . Các ví dụ tiếp theo minh họa điểm yếu này.

Trong dòng đầu tiên, bản cập nhật sẽ đặt cột “ tên ” trên tất cả các hàng thành “ hacker ”, vì bất kỳ thứ gì sau “-” được hiểu là một nhận xét trong SQL và các điều kiện của câu lệnh cập nhật sẽ bị bỏ qua. Trong dòng thứ hai, phần chèn sẽ không thành công vì phần trích dẫn trên cột “ tên ” chưa được thoát:

dao.update(new PersonEntity(1, "hacker' --")); dao.insert(new PersonEntity(1, "O'Brien"))

Thứ ba, JDBC chuyển truy vấn với các giá trị nội tuyến đến cơ sở dữ liệu . Do đó, không có tối ưu hóa truy vấn và quan trọng nhất, công cụ cơ sở dữ liệu phải đảm bảo tất cả các kiểm tra . Ngoài ra, truy vấn sẽ không xuất hiện giống với cơ sở dữ liệu và nó sẽ ngăn việc sử dụng bộ nhớ cache . Tương tự, các bản cập nhật hàng loạt cần được thực hiện riêng biệt:

public void insert(List personEntities) { for (PersonEntity personEntity: personEntities) { insert(personEntity); } }

Bốn là, các Tuyên bố giao diện phù hợp cho các truy vấn DDL như CREATE, ALTER, và thả :

public void createTables() { String query = "create table if not exists PERSONS (ID INT, NAME VARCHAR(45))"; connection.createStatement().executeUpdate(query); }

Cuối cùng, các Tuyên bố giao diện không thể được sử dụng để lưu trữ và lấy các tập tin và mảng .

4. Chuẩn bị sẵn sàng

Đầu tiên, PreparedStatement mở rộng giao diện Statement . Nó có các phương thức để liên kết các kiểu đối tượng khác nhau , bao gồm các tệp và mảng. Do đó, mã trở nên dễ hiểu :

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.executeUpdate(); }

Thứ hai, nó bảo vệ chống lại SQL injection , bằng cách thoát văn bản cho tất cả các giá trị tham số được cung cấp:

@Test void whenInsertAPersonWithQuoteInText_thenItNeverThrowsAnException() { assertDoesNotThrow(() -> dao.insert(new PersonEntity(1, "O'Brien"))); } @Test void whenAHackerUpdateAPerson_thenItUpdatesTheTargetedPerson() throws SQLException { dao.insert(Arrays.asList(new PersonEntity(1, "john"), new PersonEntity(2, "skeet"))); dao.update(new PersonEntity(1, "hacker' --")); List result = dao.getAll(); assertEquals(Arrays.asList( new PersonEntity(1, "hacker' --"), new PersonEntity(2, "skeet")), result); }

Thứ ba, PreparedStatement sử dụng tiền biên dịch . Ngay sau khi cơ sở dữ liệu nhận được một truy vấn, nó sẽ kiểm tra bộ đệm trước khi biên dịch trước truy vấn. Do đó, nếu nó không được lưu vào bộ nhớ cache, cơ sở dữ liệu sẽ lưu nó cho lần sử dụng tiếp theo.

Hơn nữa, tính năng này tăng tốc độ giao tiếp giữa cơ sở dữ liệu và JVM thông qua một giao thức nhị phân không phải SQL. Có nghĩa là, có ít dữ liệu hơn trong các gói, do đó, giao tiếp giữa các máy chủ diễn ra nhanh hơn.

Thứ tư, PreparedStatement cung cấp một thực thi hàng loạt trong một kết nối cơ sở dữ liệu duy nhất . Hãy xem điều này trong hành động:

public void insert(List personEntities) throws SQLException { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); for (PersonEntity personEntity: personEntities) { preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.addBatch(); } preparedStatement.executeBatch(); }

Tiếp theo, PreparedStatement cung cấp một cách dễ dàng để lưu trữ và truy xuất tệp bằng cách sử dụng kiểu dữ liệu BLOBCLOB . Tương tự như vậy, nó giúp lưu trữ danh sách bằng cách chuyển đổi java.sql.Array thành Mảng SQL.

Cuối cùng, PreparedStatement triển khai các phương thức như getMetadata () chứa thông tin về kết quả trả về.

5. Kết luận

Trong hướng dẫn này, chúng tôi đã trình bày những điểm khác biệt chính giữa PreparedStatementStatement . Cả hai giao diện đều cung cấp các phương thức để thực thi các truy vấn SQL, nhưng sẽ phù hợp hơn khi sử dụng Statement cho các truy vấn DDL và PreparedStatement cho các truy vấn DML.

Như thường lệ, tất cả các ví dụ về mã đều có sẵn trên GitHub.

Java dưới cùng

Tôi vừa công bố khóa học Learn Spring mới , tập trung vào các nguyên tắc cơ bản của Spring 5 và Spring Boot 2:

>> KIỂM TRA KHÓA HỌC