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


222

Tuyên bố đã chuẩn bị là một phiên bản mạnh mẽ hơn một chút của Tuyên bố và phải luôn luôn ít nhất là nhanh chóng và dễ xử lý như một Tuyên bố.
Tuyên bố đã chuẩn bị có thể được tham số hóa

Hầu hết các cơ sở dữ liệu quan hệ xử lý truy vấn JDBC / SQL theo bốn bước:

  1. Phân tích truy vấn SQL đến
  2. Biên dịch truy vấn SQL
  3. Lập kế hoạch / tối ưu hóa đường dẫn thu thập dữ liệu
  4. Thực hiện truy vấn / thu thập và trả về dữ liệu được tối ưu hóa

Một tuyên bố sẽ luôn tiến hành qua bốn bước trên cho mỗi truy vấn SQL được gửi đến cơ sở dữ liệu. Tuyên bố đã chuẩn bị trước khi thực hiện các bước (1) - (3) trong quy trình thực hiện ở trên. Do đó, khi tạo Tuyên bố đã chuẩn bị, một số tối ưu hóa trước được thực hiện ngay lập tức. Hiệu quả là giảm tải cho công cụ cơ sở dữ liệu tại thời điểm thực hiện.

Bây giờ câu hỏi của tôi là - "Có bất kỳ lợi thế nào khác của việc sử dụng Tuyên bố đã chuẩn bị không?"


12
Theo tôi, cách hiệu quả nhất là truy vấn của bạn có thể được tham số hóa một cách linh hoạt
Hussain Akhtar Wahid 'Ghouri'

Câu trả lời:


198

Ưu điểm của PreparedStatement:

  • Biên dịch trước và bộ nhớ đệm phía DB của câu lệnh SQL dẫn đến thực thi tổng thể nhanh hơn và khả năng sử dụng lại cùng một câu lệnh SQL theo lô .

  • Tự động ngăn chặn các cuộc tấn công SQL SQL bằng cách dựng sẵn các dấu ngoặc kép và các ký tự đặc biệt khác. Lưu ý rằng điều này yêu cầu bạn sử dụng bất kỳ phương thức nào để đặt giá trịPreparedStatement setXxx()

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    preparedStatement.setString(1, person.getName());
    preparedStatement.setString(2, person.getEmail());
    preparedStatement.setTimestamp(3, new Timestamp(person.getBirthdate().getTime()));
    preparedStatement.setBinaryStream(4, person.getPhoto());
    preparedStatement.executeUpdate();

    và do đó không nội tuyến các giá trị trong chuỗi SQL bằng cách nối chuỗi.

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
  • Giúp giảm bớt thiết của các đối tượng Java phi tiêu chuẩn trong một chuỗi SQL, ví dụ như Date, Time, Timestamp, BigDecimal, InputStream( Blob) và Reader( Clob). Trên hầu hết các loại bạn không thể "chỉ" làm toString()như bạn làm một cách đơn giản Statement. Bạn thậm chí có thể cấu trúc lại tất cả để sử dụng PreparedStatement#setObject()bên trong một vòng lặp như được trình bày trong phương thức tiện ích dưới đây:

    public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            preparedStatement.setObject(i + 1, values[i]);
        }
    }

    Có thể được sử dụng như dưới đây:

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    setValues(preparedStatement, person.getName(), person.getEmail(), new Timestamp(person.getBirthdate().getTime()), person.getPhoto());
    preparedStatement.executeUpdate();

4
Một văn bản mô tả và giải thích, cùng với các tài liệu tham khảo và ví dụ, làm cho một câu trả lời tuyệt vời. +1
XenoRo

1
@RD Điều này có thể đúng vì một câu lệnh được chuẩn bị yêu cầu 2 chuyến đi khứ hồi đến cơ sở dữ liệu: lần đầu tiên chuẩn bị, lần thứ hai để thực thi. Tuy nhiên, tôi sẽ kiểm tra nó. Tôi đoán rằng kế hoạch vẫn sẽ được lưu trữ trong máy chủ cơ sở dữ liệu cho một Statement, nhưng nó có thể đáng để thử nghiệm.
Brandon

2
Tôi không thể nói chắc chắn với Java, nhưng nói chung, một tuyên bố được chuẩn bị không thể hiện "thoát khỏi dấu ngoặc kép và các ký tự đặc biệt khác"; thay vào đó, nó thực hiện phân tách SQL và dữ liệu thực thi , gửi các tham số đến DBMS dưới dạng các gói thông tin riêng biệt sau khi SQL được chuyển đổi thành một kế hoạch truy vấn.
IMSoP

@BalusC - Cảm ơn bạn đã giải thích chi tiết.
CodeBee ..

49
  1. Chúng được biên dịch sẵn (một lần), vì vậy nhanh hơn để thực thi lặp lại SQL động (nơi thay đổi tham số)

  2. Bộ nhớ đệm câu lệnh cơ sở dữ liệu tăng hiệu năng thực thi DB

    Cơ sở dữ liệu lưu trữ bộ nhớ cache của kế hoạch thực hiện cho các câu lệnh được thực hiện trước đó. Điều này cho phép công cụ cơ sở dữ liệu sử dụng lại các kế hoạch cho các câu lệnh đã được thực hiện trước đó. Vì PreparedStatement sử dụng các tham số, nên mỗi lần nó được thực thi, nó xuất hiện dưới dạng cùng một SQL, cơ sở dữ liệu có thể sử dụng lại gói truy cập trước đó, giảm xử lý. Các câu lệnh "nội tuyến" các tham số vào chuỗi SQL và do đó không xuất hiện dưới dạng cùng một SQL với DB, ngăn chặn việc sử dụng bộ đệm.

  3. Giao thức truyền thông nhị phân có nghĩa là băng thông ít hơn và các cuộc gọi đến máy chủ DB nhanh hơn

    Các câu lệnh được chuẩn bị thường được thực hiện thông qua một giao thức nhị phân không phải là SQL. Điều này có nghĩa là có ít dữ liệu hơn trong các gói, vì vậy việc liên lạc đến máy chủ sẽ nhanh hơn. Theo quy tắc hoạt động của mạng ngón tay cái là một trật tự có độ lớn chậm hơn so với hoạt động của đĩa, thứ tự có độ lớn chậm hơn so với hoạt động của CPU trong bộ nhớ. Do đó, bất kỳ việc giảm lượng dữ liệu nào được gửi qua mạng sẽ có tác động tốt đến hiệu suất tổng thể.

  4. Chúng bảo vệ chống lại SQL SQL, bằng cách thoát văn bản cho tất cả các giá trị tham số được cung cấp.

  5. Chúng cung cấp sự phân tách mạnh hơn giữa mã truy vấn và các giá trị tham số (so với các chuỗi SQL được nối), tăng khả năng đọc và giúp người duy trì mã nhanh chóng hiểu được đầu vào và đầu ra của truy vấn.

  6. Trong java, có thể gọi getMetadata () và getParameterMetadata () để phản ánh trên các trường tập kết quả và các trường tham số, tương ứng

  7. Trong java, thông minh chấp nhận các đối tượng java làm các loại tham số thông qua setObject, setBoolean, setByte, setDate, setDouble, setDouble, setFloat, setInt, setLong, setShort, setTime, setTimestamp - nó chuyển đổi thành định dạng JDBC () định dạng).

  8. Trong java, chấp nhận SQL ARRAY, làm kiểu tham số thông qua phương thức setArray

  9. Trong java, chấp nhận CLOBs, BLOB, OutputStreams và Readers làm tham số "feed" thông qua các phương thức setClob / setNClob, setBlob, setBinaryStream, setCharacterStream / setAsciiStream / setNCharacterStream

  10. Trong java, cho phép các giá trị dành riêng cho DB được đặt cho SQL DATALINK, SQL ROWID, SQL XML và NULL thông qua setURL, setRowId, setQueryXML và các phương thức setNull

  11. Trong java, kế thừa tất cả các phương thức từ Statement. Nó kế thừa phương thức addBatch và thêm vào đó cho phép thêm một tập hợp các giá trị tham số để khớp với tập hợp các lệnh SQL được bó theo phương thức addBatch.

  12. Trong java, một loại PreparedStatement đặc biệt (lớp con CallableStatement) cho phép các thủ tục được lưu trữ được thực thi - hỗ trợ hiệu suất cao, đóng gói, lập trình thủ tục và SQL, quản trị / bảo trì / điều chỉnh logic của logic và sử dụng các tính năng & logic DB độc quyền


Làm thế nào là tất cả những điều kỳ diệu có thể khi cả hai chỉ là giao diện?!?!
Rafael

1
Các 'kỳ quan' được thực hiện thông qua các phương thức nhà máy tiêu chuẩn trả về việc triển khai (dành riêng cho nhà cung cấp) các giao diện: Connection.createStatementConnection.prepareStatement. Thiết kế này buộc bạn phải làm việc với các giao diện để bạn không cần biết các lớp triển khai cụ thể và để tránh sự kết hợp chặt chẽ không cần thiết với các lớp thực hiện đó. Tất cả được giải thích với các ví dụ trong tài liệu Java jdbc & tài liệu Java. :)
Glen tốt nhất

Phần "như một quy tắc của ngón tay cái" của bạn không có ý nghĩa gì khác không phải là cách khác 🤔
bhathiya-perera

38

PreparedStatementlà một cách phòng thủ rất tốt (nhưng không thể đánh lừa) trong việc ngăn chặn các cuộc tấn công tiêm nhiễm SQL . Các giá trị tham số ràng buộc là một cách tốt để bảo vệ chống lại "Bàn Bobby nhỏ" thực hiện một chuyến thăm không mong muốn.


6
Làm thế nào một người sẽ thực hiện tiêm SQL thông qua một tuyên bố chuẩn bị sau đó?
Michael Borgwardt

2
Michael, Biến được chuyển làm đối số cho các câu lệnh được chuẩn bị sẽ tự động thoát khỏi trình điều khiển JDBC.
CodeBee ..

3
Bạn có thể đưa ra một ví dụ về cách một cuộc tấn công tiêm SQL sẽ hoạt động chống lại một tuyên bố đã chuẩn bị không? Bạn đang giả sử một lỗi trong mã cơ sở dữ liệu?
Peter Recore

2
Vâng, nhưng nó vượt xa "khá ngu ngốc". Đó là tâm trí ngu ngốc. Không ai có một ounce kiến ​​thức sẽ làm điều đó.
duffymo

2
Ngoài ra, nhiều nhà cung cấp cơ sở dữ liệu không hỗ trợ parameterizing tên cột (suy nghĩ ORDER BY) và / hoặc các hằng số ở những nơi nhất định (suy nghĩ LIMIT, OFFSETvà các giải pháp pagination khác), vì vậy chúng có thể bị tấn công bởi SQL injection, ngay cả khi chuẩn bị phát biểu và tham số được sử dụng bất cứ nơi nào khả thi.
dnet

31

Một số lợi ích của PreparedStatement over Statement là:

  1. PreparedStatement giúp chúng tôi ngăn chặn các cuộc tấn công SQL SQL vì nó tự động thoát khỏi các ký tự đặc biệt.
  2. PreparedStatement cho phép chúng tôi thực hiện các truy vấn động với các đầu vào tham số.
  3. PreparedStatement cung cấp các loại phương thức setter khác nhau để đặt tham số đầu vào cho truy vấn.
  4. PreparedStatement nhanh hơn Statement. Nó trở nên rõ ràng hơn khi chúng ta sử dụng lại PreparedStatement hoặc sử dụng các phương thức xử lý hàng loạt của nó để thực hiện nhiều truy vấn.
  5. PreparedStatement giúp chúng ta viết mã hướng đối tượng bằng các phương thức setter trong khi với Statement chúng ta phải sử dụng Chuỗi kết hợp để tạo truy vấn. Nếu có nhiều tham số cần đặt, viết Truy vấn bằng cách sử dụng nối chuỗi có vẻ rất xấu và dễ bị lỗi.

Đọc thêm về vấn đề tiêm SQL tại http://www.journaldev.com/2361/jdbc-statement-vs-preparedstatement-sql-injection-example


Tôi đọc bài viết của bạn, thực sự tốt một. Câu hỏi của tôi bây giờ là tại sao mọi người sẽ sử dụng Statement?! ngay cả đối với một truy vấn tĩnh?!
pedram bashiri

Tôi luôn sử dụng PreparedStatement, tôi không biết bất kỳ kịch bản cụ thể nào mà Statement có thể có nhiều lợi ích hơn.
Pankaj

13

không có gì nhiều để thêm

1 - nếu bạn muốn thực hiện một truy vấn trong một vòng lặp (hơn 1 lần), câu lệnh được chuẩn bị có thể nhanh hơn, vì tối ưu hóa mà bạn đã đề cập.

2 - truy vấn được tham số hóa là một cách tốt để tránh SQL Injection. Các truy vấn được tham số hóa chỉ có sẵn trong PreparedStatement.


10

Tuyên bố là tĩnh và chuẩn bị là động.

Tuyên bố phù hợp với DDL và chuẩn bị cho DML.

Tuyên bố là chậm hơn trong khi tuyên bố chuẩn bị là nhanh hơn.

nhiều sự khác biệt (được lưu trữ)


7

Không thể thực hiện CLOB trong Tuyên bố.

Và: (OraclePreparedStatement) ps


7

Được trích dẫn bởi mattjames

Việc sử dụng Tuyên bố trong JDBC phải được bản địa hóa 100% để được sử dụng cho DDL (ALTER, CREATE, GRANT, v.v.) vì đây là những loại câu lệnh duy nhất không thể chấp nhận BIND VARIABLES. PreparedStatements hoặc CallableStatements nên được sử dụng cho MỌI loại câu lệnh KHÁC (DML, Truy vấn). Vì đây là các loại câu lệnh chấp nhận các biến ràng buộc.

Đây là một thực tế, một quy tắc, một luật - sử dụng các tuyên bố đã chuẩn bị MỌI NƠI. Sử dụng TUYÊN BỐ hầu như không có ở đâu.


5

tiêm sql bị bỏ qua bởi tuyên bố chuẩn bị để bảo mật được tăng lên trong tuyên bố chuẩn bị


4
  • Nó dễ đọc hơn
  • Bạn có thể dễ dàng làm cho chuỗi truy vấn không đổi

4

Câu lệnh sẽ được sử dụng để thực thi các câu lệnh SQL tĩnh và nó không thể chấp nhận các tham số đầu vào.

PreparedStatement sẽ được sử dụng để thực thi các câu lệnh SQL nhiều lần. Nó sẽ chấp nhận các tham số đầu vào.


4

Một đặc điểm khác của Truy vấn Chuẩn bị hoặc Tham số: Tham chiếu được lấy từ bài viết này.

Câu lệnh này là một trong những tính năng của hệ thống cơ sở dữ liệu trong đó cùng một câu lệnh SQL thực thi lặp lại với hiệu quả cao. Các báo cáo được chuẩn bị là một loại Mẫu và được ứng dụng sử dụng với các tham số khác nhau.

Mẫu câu lệnh được chuẩn bị và gửi đến hệ thống cơ sở dữ liệu và hệ thống cơ sở dữ liệu thực hiện phân tích cú pháp, biên dịch và tối ưu hóa trên mẫu này và lưu trữ mà không thực hiện nó.

Một số tham số như, mệnh đề không được truyền trong ứng dụng tạo mẫu sau này, gửi các tham số này đến hệ thống cơ sở dữ liệu và hệ thống cơ sở dữ liệu sử dụng mẫu của Câu lệnh SQL và thực thi theo yêu cầu.

Các câu lệnh được chuẩn bị rất hữu ích để chống lại SQL Injection vì ứng dụng có thể chuẩn bị tham số bằng các kỹ thuật và giao thức khác nhau.

Khi số lượng dữ liệu đang tăng lên và các chỉ mục thay đổi thường xuyên tại thời điểm đó Báo cáo đã chuẩn bị có thể không thành công vì trong tình huống này yêu cầu một kế hoạch truy vấn mới.


3

Statement giao diện thực thi các câu lệnh SQL tĩnh không có tham số

PreparedStatement giao diện (mở rộng câu lệnh) thực thi một câu lệnh SQL được biên dịch sẵn có / không có tham số

  1. Hiệu quả cho các lần thực hiện lặp đi lặp lại

  2. Nó được biên dịch trước để nó nhanh hơn


2

Đừng nhầm lẫn: chỉ cần nhớ

  1. Câu lệnh được sử dụng cho các truy vấn tĩnh như DDL, tức là tạo, thả, thay đổi và chuẩn bị được sử dụng cho các truy vấn động tức là truy vấn DML.
  2. Trong Statement, truy vấn không được biên dịch trước trong khi trong truy vấn chuẩn bị được biên dịch trước, bởi vì chuẩn bị này là thời gian hiệu quả.
  3. readyStatement lấy đối số tại thời điểm tạo trong khi Statement không lấy đối số. Ví dụ: nếu bạn muốn tạo bảng và chèn phần tử thì :: Tạo bảng (tĩnh) bằng cách sử dụng phần tử Statement và Chèn (động) bằng cách sử dụng readyStatement.

1
readyStatement lấy đối số tại thời điểm tạo trong khi Statement không lấy đối số.?

1

Tôi đã làm theo tất cả các câu trả lời của câu hỏi này để thay đổi mã kế thừa đang hoạt động bằng cách sử dụng - Statement(nhưng có SQL Tiêm) thành một giải pháp sử dụng PreparedStatementvới mã chậm hơn nhiều do hiểu biết kém về ngữ nghĩa xung quanh Statement.addBatch(String sql)&PreparedStatement.addBatch() .

Vì vậy, tôi liệt kê kịch bản của mình ở đây để những người khác không mắc lỗi tương tự.

Kịch bản của tôi là

Statement statement = connection.createStatement();

for (Object object : objectList) {
    //Create a query which would be different for each object 
    // Add this query to statement for batch using - statement.addBatch(query);
}
statement.executeBatch();

Vì vậy, trong đoạn mã trên, tôi có hàng ngàn truy vấn khác nhau, tất cả được thêm vào cùng một câu lệnh và mã này hoạt động nhanh hơn vì các câu lệnh không được lưu trong bộ nhớ cache là tốt và mã này hiếm khi được thực thi trong ứng dụng.

Bây giờ để sửa lỗi SQL, tôi đã thay đổi mã này thành,

List<PreparedStatement> pStatements = new ArrayList<>();    
for (Object object : objectList) {
    //Create a query which would be different for each object 
    PreparedStatement pStatement =connection.prepareStatement(query);
    // This query can't be added to batch because its a different query so I used list. 
    //Set parameter to pStatement using object 
    pStatements.add(pStatement);
}// Object loop
// In place of statement.executeBatch(); , I had to loop around the list & execute each update separately          
for (PreparedStatement ps : pStatements) {
    ps.executeUpdate();
}

Vì vậy, bạn thấy, tôi đã bắt đầu tạo ra hàng ngàn PreparedStatementđối tượng và cuối cùng không thể sử dụng theo đợt vì kịch bản của tôi yêu cầu - có hàng ngàn truy vấn CẬP NHẬT hoặc CHERTN và tất cả các truy vấn này đều khác nhau.

Sửa lỗi SQL SQL là bắt buộc mà không phải trả chi phí cho sự suy giảm hiệu năng và tôi không nghĩ rằng điều đó là có thể với PreparedStatementkịch bản này.

Ngoài ra, khi bạn sử dụng cơ sở xử lý theo khối sẵn có, bạn phải lo lắng về việc chỉ đóng một Tuyên bố nhưng với cách tiếp cận Danh sách này, bạn cần đóng câu lệnh trước khi sử dụng lại, Sử dụng lại Chuẩn bị sẵn sàng

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.