ความแตกต่างระหว่าง Statement และ PreparedStatement


222

แถลงการณ์ที่เตรียมไว้เป็นแถลงการณ์ที่มีประสิทธิภาพมากกว่าเล็กน้อยและควรมีอย่างน้อยเร็วและง่ายต่อการจัดการเหมือนแถลงการณ์
คำแถลงที่เตรียมไว้อาจถูกตีความได้

ฐานข้อมูลเชิงสัมพันธ์ส่วนใหญ่จัดการแบบสอบถาม JDBC / SQL ในสี่ขั้นตอน:

  1. แยกแบบสอบถาม SQL ขาเข้า
  2. รวบรวมแบบสอบถาม SQL
  3. วางแผน / ปรับเส้นทางการเก็บข้อมูลให้เหมาะสม
  4. ดำเนินการค้นหา / รับและส่งคืนข้อมูลที่ดีที่สุด

คำชี้แจงจะดำเนินการผ่านสี่ขั้นตอนด้านบนสำหรับแบบสอบถาม SQL แต่ละรายการที่ส่งไปยังฐานข้อมูลเสมอ คำชี้แจงที่เตรียมไว้ล่วงหน้าจะดำเนินการตามขั้นตอน (1) - (3) ในกระบวนการดำเนินการข้างต้น ดังนั้นเมื่อสร้างคำสั่งที่เตรียมไว้จะมีการดำเนินการเพิ่มประสิทธิภาพล่วงหน้าทันที ผลที่ได้คือการลดภาระให้กับเอ็นจินฐานข้อมูล ณ เวลาดำเนินการ

ตอนนี้คำถามของฉันคือ - "ประโยชน์อื่น ๆ ของการใช้งบเตรียม?"


12
ที่มีประสิทธิภาพที่สุดตามฉันคือการสอบถามของคุณสามารถแปรแบบไดนามิก
Hussain Akhtar Wahid 'Ghouri'

คำตอบ:


198

ข้อดีของPreparedStatement:

  • Precompilation และ DB-แคชฝั่งของนำไปสู่คำสั่ง SQL โดยรวมเร็วขึ้นการดำเนินการและความสามารถเพื่อนำมาใช้คำสั่ง SQL เดียวกันในbatches

  • การป้องกันการ โจมตีSQL injectionโดยอัตโนมัติโดยการสร้างเครื่องหมายคำพูดและอักขระพิเศษอื่น ๆ โปรดทราบว่าสิ่งนี้ต้องการให้คุณใช้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();
    

    และดังนั้นอย่าอินไลน์ค่าในสตริง SQL โดยการต่อสตริงเข้าด้วยกัน

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
    
  • eases การตั้งค่าของที่ไม่ได้มาตรฐานวัตถุ Java ในสตริง SQL เช่นDate, Time, Timestamp, BigDecimal, InputStream( Blob) และReader( Clob) ในส่วนของประเภทที่คุณจะไม่สามารถ "เพียงแค่" ทำตามที่คุณจะทำอย่างไรในที่เรียบง่ายtoString() Statementคุณสามารถปรับโครงสร้างทั้งหมดเพื่อใช้PreparedStatement#setObject()ภายในลูปดังแสดงในวิธีการยูทิลิตี้ด้านล่าง:

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

    ซึ่งสามารถใช้ดังนี้:

    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
ข้อความอธิบายและอธิบายประกอบกับการอ้างอิงและตัวอย่างทำให้คำตอบที่ยอดเยี่ยม +1
XenoRo

1
@RD นี่อาจเป็นจริงเพราะคำสั่งที่เตรียมไว้นั้นต้องการการเดินทางไปกลับ 2 ครั้งไปยังฐานข้อมูล: คำสั่งแรกในการเตรียมการครั้งที่สองที่จะดำเนินการ อย่างไรก็ตามฉันจะทดสอบมัน ฉันคิดว่าแผนจะยังคงถูกแคชในเซิร์ฟเวอร์ฐานข้อมูลสำหรับStatementแต่มันอาจคุ้มค่าการทดสอบ
Brandon

2
ฉันไม่สามารถพูดได้อย่างแน่นอนกับ Java แต่โดยทั่วไปแล้วคำสั่งที่เตรียมไว้ไม่ได้ preform "builtin หนีจากคำพูดและอักขระพิเศษอื่น ๆ "; แต่จะดำเนินการแยกของ SQL ที่เรียกใช้งานได้และข้อมูลโดยส่งพารามิเตอร์ไปยัง DBMS เป็นแพ็กเก็ตข้อมูลแยกต่างหากหลังจากที่ SQL ถูกแปลงเป็นแผนแบบสอบถาม
IMSoP

@BalusC - ขอบคุณสำหรับคำอธิบายโดยละเอียด
CodeBee ..

49
  1. พวกเขาจะรวบรวมไว้ล่วงหน้า (หนึ่งครั้ง) เร็วขึ้นสำหรับการดำเนินการของ SQL แบบไดนามิกซ้ำ ๆ

  2. การแคชคำสั่งฐานข้อมูลช่วยเพิ่มประสิทธิภาพการดำเนินการฐานข้อมูล

    ฐานข้อมูลเก็บแคชแผนการดำเนินการสำหรับคำสั่งที่เรียกใช้ก่อนหน้านี้ สิ่งนี้ทำให้เอ็นจิ้นฐานข้อมูลสามารถใช้แผนสำหรับงบที่ถูกดำเนินการก่อนหน้านี้อีกครั้ง เนื่องจาก PreparedStatement ใช้พารามิเตอร์แต่ละครั้งที่มีการดำเนินการจะปรากฏเป็น SQL เดียวกันฐานข้อมูลสามารถใช้แผนการเข้าถึงก่อนหน้านี้อีกครั้งเพื่อลดการประมวลผล คำสั่ง "inline" พารามิเตอร์ลงในสตริง SQL และไม่ปรากฏเป็น SQL เดียวกันกับฐานข้อมูลเพื่อป้องกันการใช้แคช

  3. โปรโตคอลการสื่อสารแบบไบนารีหมายถึงแบนด์วิดท์ที่น้อยลงและการโทรแบบเร็วไปยังเซิร์ฟเวอร์ DB

    คำสั่งที่เตรียมไว้จะดำเนินการตามปกติผ่านโปรโตคอลไบนารีที่ไม่ใช่ SQL ซึ่งหมายความว่ามีข้อมูลน้อยลงในแพ็กเก็ตดังนั้นการสื่อสารกับเซิร์ฟเวอร์จึงเร็วขึ้น ตามกฎของการดำเนินงานเครือข่ายนิ้วหัวแม่มือเป็นลำดับความสำคัญช้ากว่าการดำเนินการของดิสก์ซึ่งเป็นลำดับความสำคัญช้ากว่าการทำงานของ CPU ในหน่วยความจำ ดังนั้นการลดจำนวนข้อมูลที่ส่งผ่านเครือข่ายจะมีผลดีต่อประสิทธิภาพโดยรวม

  4. พวกเขาป้องกันการฉีด SQL โดยหนีจากข้อความสำหรับค่าพารามิเตอร์ทั้งหมดที่มีให้

  5. พวกเขาให้แยกที่แข็งแกร่งระหว่างรหัสแบบสอบถามและค่าพารามิเตอร์ (เปรียบเทียบกับสตริง SQL ที่ต่อกัน) เพิ่มความสามารถในการอ่านและช่วยให้ผู้ดูแลรหัสเข้าใจข้อมูลอินพุตและเอาต์พุตของแบบสอบถามได้อย่างรวดเร็ว

  6. ใน java สามารถเรียกใช้ getMetadata () และ getParameterMetadata () เพื่อแสดงผลในฟิลด์ชุดผลลัพธ์และฟิลด์พารามิเตอร์ตามลำดับ

  7. ใน java ยอมรับอย่างฉลาดว่าวัตถุ java เป็นชนิดพารามิเตอร์ผ่าน setObject, setBoolean, setByte, setDate, setDouble, setDouble, setDouble, setFloat, setInt, setLong, setShort, setTime, setTimestamp - มันแปลงเป็นรูปแบบประเภท JDBC () รูปแบบ).

  8. ใน java ยอมรับ SQL ARRAYs เป็นชนิดพารามิเตอร์ผ่านวิธี setArray

  9. ใน Java ยอมรับ CLOBs, BLOBs, OutputStreams และ Readers เป็นพารามิเตอร์ "ฟีด" ผ่าน setClob / setNClob, setBlob, setBinaryStream, setCharacterStream / setAsciiStream / setNCharacterStream ตามลำดับ

  10. ใน java อนุญาตให้ตั้งค่าฐานข้อมูลเฉพาะสำหรับ SQL DATALINK, SQL ROWID, SQL XML และ NULL ผ่าน setURL, setRowId, setSQLXML และวิธี setNull

  11. ใน java สืบทอดวิธีการทั้งหมดจาก Statement มันสืบทอดเมธอด addBatch และอนุญาตให้เพิ่มชุดของค่าพารามิเตอร์เพื่อให้ตรงกับชุดของคำสั่ง SQL ที่แบตช์ผ่านเมธอด addBatch

  12. ในจาวาชนิดพิเศษของ PreparedStatement (subclass CallableStatement) อนุญาตให้ดำเนินการขั้นตอนการจัดเก็บ - รองรับประสิทธิภาพสูง, การห่อหุ้ม, การเขียนโปรแกรมขั้นตอนและ SQL, การบริหารฐานข้อมูล / การบำรุงรักษา / การปรับแต่งตรรกะและการใช้ตรรกะ DB และคุณสมบัติที่เป็นกรรมสิทธิ์


สิ่งมหัศจรรย์เหล่านั้นเป็นไปได้อย่างไรเมื่อทั้งคู่เป็นเพียงอินเทอร์เฟซ?!?!
ราฟาเอล

1
'ความมหัศจรรย์' จะทำไปผ่านทางวิธีการโรงงานมาตรฐานว่าการกลับมา (เฉพาะผู้ขาย) การใช้งานของอินเตอร์เฟซ: และConnection.createStatement Connection.prepareStatementการออกแบบนี้บังคับให้คุณทำงานกับอินเทอร์เฟซดังนั้นคุณไม่จำเป็นต้องรู้คลาสการใช้งานที่เฉพาะเจาะจงและเพื่อหลีกเลี่ยงการเชื่อมต่อที่ไม่จำเป็นกับคลาสการใช้งานดังกล่าว ทั้งหมดอธิบายด้วยตัวอย่างในเอกสาร Java jdbc และเอกสาร Java :)
เกลนที่ดีที่สุด

ส่วน "เป็นกฎง่ายๆ" ของคุณไม่สมเหตุสมผล
ใช่มั้

38

PreparedStatementคือการป้องกันที่ดีมาก ( แต่ไม่สามารถจะเข้าใจผิด) ในการป้องกันการโจมตีฉีด SQL ค่าพารามิเตอร์การผูกเป็นวิธีที่ดีในการป้องกัน"ตาราง Bobby เล็กน้อย"ซึ่งเป็นการเยี่ยมชมที่ไม่พึงประสงค์


6
วิธีการอย่างใดอย่างหนึ่งทำการฉีด SQL ผ่านคำสั่งที่เตรียมไว้แล้ว?
Michael Borgwardt

2
Michael, ตัวแปรที่ส่งผ่านเป็นอาร์กิวเมนต์ไปยังคำสั่งที่เตรียมไว้จะถูกหลบหนีโดยไดรเวอร์ JDBC โดยอัตโนมัติ
CodeBee ..

3
คุณช่วยยกตัวอย่างว่า SQL injection injection จะทำงานกับคำสั่งที่เตรียมไว้ได้อย่างไร? คุณสมมติว่ามีข้อผิดพลาดในรหัสฐานข้อมูลหรือไม่
Peter Recore

2
ใช่ แต่มันไกลเกินกว่าที่จะเป็น "หุ่นสวย" มันเป็นความคิดที่โง่เขลา ไม่มีใครที่มีความรู้ออนซ์จะทำเช่นนั้น
duffymo

2
นอกจากนี้ผู้ผลิตฐานข้อมูลจำนวนมากไม่สนับสนุน parameterizing ชื่อคอลัมน์ที่ (คิดว่าORDER BY) และ / หรือค่าคงที่ตัวเลขในบางสถานที่ (คิดว่าLIMIT, OFFSETและการแก้ปัญหาการแบ่งหน้าอื่น ๆ ) ดังนั้นเหล่านี้สามารถโจมตีโดยฉีด SQL แม้ในขณะที่งบเตรียมและ parameterization จะใช้ที่ใดก็ตาม เป็นไปได้
dnet

31

ประโยชน์บางส่วนของ PreparedStatement over Statement คือ:

  1. PreparedStatement ช่วยเราในการป้องกันการโจมตีด้วยการฉีด SQL เพราะมันจะหนีอักขระพิเศษโดยอัตโนมัติ
  2. PreparedStatement ช่วยให้เราสามารถรันเคียวรีแบบไดนามิกด้วยอินพุตพารามิเตอร์
  3. PreparedStatement จัดเตรียมวิธี setter ประเภทต่างๆเพื่อตั้งค่าพารามิเตอร์อินพุตสำหรับเคียวรี
  4. PreparedStatement เร็วกว่า Statement จะปรากฏให้เห็นได้มากขึ้นเมื่อเราใช้ PreparedStatement หรือใช้วิธีการประมวลผลแบบแบทช์สำหรับการดำเนินการหลายแบบสอบถาม
  5. PreparedStatement ช่วยเราในการเขียน object Oriented code ด้วยเมธอด setter ในขณะที่ Statement เราต้องใช้ String Concatenation เพื่อสร้างการสืบค้น หากมีหลายพารามิเตอร์ที่จะตั้งค่าการเขียน Query โดยใช้การต่อข้อมูลสตริงจะดูน่าเกลียดมากและเกิดข้อผิดพลาดได้ง่าย

อ่านเพิ่มเติมเกี่ยวกับปัญหาการฉีด SQL ที่http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-inject-example


ฉันอ่านบทความของคุณดีมาก คำถามของฉันตอนนี้คือเหตุผลที่ทุกคนจะใช้คำชี้แจง! แม้สำหรับแบบสอบถามแบบคงที่!
pedram bashiri

ฉันใช้ PreparedStatement เสมอฉันไม่ทราบว่ามีสถานการณ์เฉพาะใดที่ Statement อาจมีประโยชน์มากกว่า
Pankaj

13

ไม่มีอะไรให้เพิ่ม

1 - ถ้าคุณต้องการรันคิวรีในลูป (มากกว่า 1 ครั้ง) คำสั่งที่เตรียมไว้อาจเร็วขึ้นเนื่องจากการปรับให้เหมาะสมที่คุณกล่าวถึง

2 - เคียวรีที่กำหนดพารามิเตอร์เป็นวิธีที่ดีในการหลีกเลี่ยง SQL Injection แบบสอบถามแบบ Parameterized มีให้เฉพาะใน PreparedStatement


10

คำแถลงเป็นแบบคงที่และสถานะที่เตรียมไว้เป็นแบบไดนามิก

คำแถลงการณ์นี้เหมาะสำหรับ DDL และการเตรียมการสำหรับ DML

คำสั่งช้ากว่าในขณะที่คำสั่งที่เตรียมไว้นั้นเร็วกว่า

ความแตกต่างเพิ่มเติม (เก็บถาวร)



7

ตามที่ยกมาโดยmattjames

การใช้ Statement ใน JDBC ควรเป็นภาษาท้องถิ่น 100% เพื่อใช้สำหรับ DDL (ALTER, CREATE, GRANT และอื่น ๆ ) เนื่องจากนี่เป็นประเภทคำสั่งเดียวที่ไม่สามารถยอมรับ BIND VARIABLES PreparedStatements หรือ CallableStatements ควรใช้กับคำสั่งประเภทอื่น ๆ (DML, Queries) เนื่องจากเหล่านี้เป็นชนิดข้อความสั่งที่ยอมรับตัวแปรผูก

นี่คือความจริง, กฎ, กฎหมาย - ใช้คำสั่งที่เตรียมไว้ทุกที่ ใช้งบเกือบจะไม่มีที่ไหน


5

การฉีด sql ถูกละเว้นโดยคำสั่งที่เตรียมไว้เพื่อความปลอดภัยจะเพิ่มขึ้นในคำสั่งที่เตรียมไว้


4
  • อ่านง่ายกว่า
  • คุณสามารถทำให้สตริงเคียวรีเป็นค่าคงที่ได้อย่างง่ายดาย

4

คำสั่งจะใช้สำหรับการดำเนินการคำสั่ง SQL แบบคงที่และไม่สามารถรับพารามิเตอร์อินพุตได้

PreparedStatement จะใช้สำหรับการดำเนินการคำสั่ง SQL หลายครั้งแบบไดนามิก มันจะยอมรับพารามิเตอร์อินพุต


4

คุณลักษณะอื่นของแบบสอบถามที่เตรียมไว้หรือพารามิเตอร์: การอ้างอิงที่นำมาจากบทความนี้

คำแถลงนี้เป็นหนึ่งในคุณสมบัติของระบบฐานข้อมูลที่คำสั่ง SQL เดียวกันรันได้หลายครั้งและมีประสิทธิภาพสูง คำสั่งที่เตรียมไว้เป็นเทมเพลตชนิดหนึ่งและใช้โดยแอปพลิเคชันที่มีพารามิเตอร์ต่างกัน

เท็มเพลตคำสั่งถูกจัดเตรียมและส่งไปยังระบบฐานข้อมูลและระบบฐานข้อมูลทำการแยกวิเคราะห์รวบรวมและเพิ่มประสิทธิภาพบนแม่แบบและร้านค้านี้โดยไม่ต้องดำเนินการ

บางส่วนของพารามิเตอร์เช่นที่ข้อไม่ผ่านในระหว่างการสร้างแม่แบบแอปพลิเคชันส่งพารามิเตอร์เหล่านี้ไปยังระบบฐานข้อมูลและระบบฐานข้อมูลใช้แม่แบบของคำสั่ง SQL และดำเนินการตามคำขอ

คำสั่งที่เตรียมไว้มีประโยชน์มากต่อการฉีด SQL เนื่องจากแอปพลิเคชันสามารถเตรียมพารามิเตอร์โดยใช้เทคนิคและโปรโตคอลที่แตกต่างกัน

เมื่อจำนวนข้อมูลเพิ่มขึ้นและดัชนีมีการเปลี่ยนแปลงบ่อยครั้ง ณ เวลานั้นงบที่เตรียมไว้อาจล้มเหลวเนื่องจากในสถานการณ์นี้ต้องมีแผนแบบสอบถามใหม่


3

Statement อินเตอร์เฟสเรียกใช้งานคำสั่ง SQL แบบสแตติกโดยไม่มีพารามิเตอร์

PreparedStatement interface (การขยาย Statement) เรียกใช้งานคำสั่ง SQL ที่คอมไพล์แล้วด้วย / ไม่มีพารามิเตอร์

  1. มีประสิทธิภาพสำหรับการดำเนินการซ้ำ

  2. มันถูกคอมไพล์ล่วงหน้าเพื่อให้เร็วขึ้น


2

อย่าสับสน: เพียงจำไว้

  1. คำสั่งที่ใช้สำหรับการค้นหาแบบคงที่เช่น DDL เช่นสร้างวางแก้ไขและ PreparStatement ใช้สำหรับการสืบค้นแบบไดนามิกเช่นแบบสอบถาม DML
  2. ในคำสั่งแบบสอบถามจะไม่ถูกคอมไพล์ล่วงหน้าในขณะที่การเตรียมการแบบสอบถามสถานะจะถูกคอมไพล์ล่วงหน้าเนื่องจากการเตรียมการสถานะนี้จะมีเวลาที่มีประสิทธิภาพ
  3. PreparStatement รับอาร์กิวเมนต์ ณ เวลาที่สร้างขณะที่ Statement ไม่รับอาร์กิวเมนต์ ตัวอย่างเช่นถ้าคุณต้องการสร้างตารางและแทรกองค์ประกอบแล้ว :: สร้างตาราง (คงที่) โดยใช้คำชี้แจงและแทรกองค์ประกอบ (ไดนามิก) โดยใช้ PreparStatement

1
PreparStatement รับการโต้แย้งในขณะที่สร้างขณะที่ Statement ไม่รับการโต้แย้ง?

1

ผมทำตามทุกคำตอบของคำถามนี้เพื่อเปลี่ยนรหัสเดิมการทำงานโดยใช้ - Statement( แต่มี SQL ฉีด) เพื่อแก้ปัญหาโดยใช้PreparedStatementด้วยรหัสช้ากว่ามากเพราะมีความเข้าใจที่ดีของความหมายรอบและStatement.addBatch(String sql)PreparedStatement.addBatch()

ดังนั้นฉันกำลังแสดงรายการสถานการณ์ของฉันที่นี่เพื่อให้ผู้อื่นไม่ทำผิดพลาดเหมือนกัน

สถานการณ์ของฉันคือ

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();

ดังนั้นในโค้ดด้านบนฉันมีข้อความค้นหาหลายพันข้อความทั้งหมดเพิ่มลงในคำสั่งเดียวกันและรหัสนี้ทำงานได้เร็วขึ้นเนื่องจากข้อความที่ไม่ได้ถูกแคชก็ดีและโค้ดนี้ทำงานไม่ค่อยในแอป

ตอนนี้เพื่อแก้ไข SQL Injections ฉันเปลี่ยนรหัสนี้เป็น

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();
}

คุณเห็นแล้วว่าฉันเริ่มสร้างPreparedStatementวัตถุหลายพันชิ้นแล้วในที่สุดก็ไม่สามารถใช้การแบทช์เพราะสถานการณ์ของฉันเรียกร้องว่า - มีแบบสอบถาม UPDATE หรือ INSERT หลายพันรายการและแบบสอบถามเหล่านี้ทั้งหมดแตกต่างกัน

การแก้ไขการฉีด SQL จำเป็นต้องมีโดยไม่มีค่าใช้จ่ายในการลดประสิทธิภาพและฉันไม่คิดว่าจะเป็นไปได้PreparedStatementในสถานการณ์นี้

นอกจากนี้เมื่อคุณใช้ระบบอำนวยความสะดวกการแบตช์ inbuilt คุณต้องกังวลเกี่ยวกับการปิดงบเพียงครั้งเดียว แต่ด้วยวิธีการแบบรายการนี้คุณจะต้องปิดคำสั่งก่อนที่จะนำมาใช้ซ้ำการใช้PreparedStatementซ้ำ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.