Різниця між заявою та підготовленою державою


222

Підготовлена ​​заява є дещо потужнішою версією заяви, і вона завжди повинна бути принаймні такою швидкою і простою, як і заява.
Підготовлена ​​заява може бути параметризована

Більшість реляційних баз даних обробляє запит JDBC / SQL в чотири етапи:

  1. Розбір вхідного запиту SQL
  2. Складіть запит SQL
  3. Плануйте / оптимізуйте шлях збору даних
  4. Виконати оптимізований запит / придбання та повернення даних

Заява завжди буде проходити через чотири етапи вище для кожного SQL-запиту, надісланого до бази даних. Підготовлена ​​заява попередньо виконує етапи (1) - (3) у процесі виконання, зазначеному вище. Таким чином, при створенні Підготовленої заяви, негайно проводиться деяка попередня оптимізація. Ефект полягає в зменшенні навантаження на двигун бази даних під час виконання.

Тепер моє запитання таке - "Чи є якась інша перевага використання підготовленої заяви?"


12
Найефективніший, на мою думку, - це те, що ваш запит можна параметризувати динамічно
Hussain Akhtar Wahid 'Ghouri'

Відповіді:


198

Переваги PreparedStatement:

  • Попередня компіляція та кешування на стороні БД оператора SQL призводить до загального швидшого виконання та можливості повторного використання одного і того ж оператора SQL у партіях .

  • Автоматична профілактика атак ін'єкцій SQL за допомогою вбудованого виходу цитат та інших спеціальних символів. Зауважте, що для цього потрібно використовувати будь-який із методів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();
    
  • Полегшує настройка нестандартних об'єктів 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, але це, можливо, варто тестувати.
Брендон

2
Я не можу сказати напевно з Java, але загалом підготовлена ​​заява не передбачає "вбудованого уникнення цитат та інших спеціальних символів"; натомість він виконує розділення виконуваних SQL та даних , надсилаючи параметри до СУБД як окремі пакети інформації після перетворення SQL у план запитів.
IMSoP

@BalusC - Дякую за детальне пояснення.
CodeBee ..

49
  1. Вони попередньо компілюються (один раз), так швидше для повторного виконання динамічного SQL (де змінюються параметри)

  2. Кешування операторів бази даних підвищує продуктивність виконання БД

    Бази даних зберігають кеші планів виконання попередньо виконаних операторів. Це дозволяє двигуну бази даних повторно використовувати плани для заяв, які були виконані раніше. Оскільки PreparedStatement використовує параметри, щоразу, коли воно виконується, воно з'являється як один і той же SQL, база даних може повторно використовувати попередній план доступу, зменшуючи обробку. Заяви "вбудовують" параметри в рядок SQL і тому не відображаються як однакові SQL до БД, що запобігає використанню кешу.

  3. Бінарний протокол комунікації означає меншу пропускну спроможність і швидше обмінюється дзвінками на сервер БД

    Підготовлені оператори, як правило, виконуються через бінарний протокол, який не є SQL. Це означає, що в пакетах є менше даних, тому зв’язок із сервером відбувається швидше. Як правило, операції з мережевими операціями - це на порядок повільніше, ніж операції з диском, які на порядок повільніші, ніж операції з процесором в пам'яті. Отже, будь-яке зменшення кількості даних, що надсилаються по мережі, матиме хороший вплив на загальну продуктивність.

  4. Вони захищають від ін'єкції SQL, уникаючи тексту для всіх наданих значень параметрів.

  5. Вони забезпечують більш чіткий поділ між кодом запиту та значеннями параметрів (порівняно з об'єднаними рядками SQL), підвищуючи читабельність та допомагаючи обслуговуючим кодам швидко розуміти входи та результати запиту.

  6. У Java може зателефонувати getMetadata () та getParameterMetadata (), щоб відобразити поля полів результату та поля параметрів відповідно

  7. У java інтелектуально приймає об’єкти java як типи параметрів через setObject, setBoolean, setByte, setDate, setDouble, setDouble, setFloat, setInt, setLong, setShort, setTime, setTimestamp - він перетворює у формат типу JDBC, зрозумілий для БД (не тільки toString () формат).

  8. У Java, приймає SQL ARRAY, як тип параметра за допомогою методу setArray

  9. У Java, приймає CLOB, BLOB, OutputStreams та Readers як параметр "канали" через setClob / setNClob, setBlob, setBinaryStream, setCharacterStream / setAsciiStream / setNCharacterStream відповідно.

  10. У Java дозволяє встановити специфічні для DB значення значення для SQL DATALINK, SQL ROWID, SQL XML та NULL через setURL, setRowId, setSQLXML ans setNull

  11. У java успадковує всі методи із Statement. Він успадковує метод addBatch і додатково дозволяє додавати набір значень параметрів, щоб відповідати набору пакетних команд SQL через метод addBatch.

  12. У Java, спеціальний тип PreparedStatement (підклас CallableStatement) дозволяє виконувати збережені процедури - підтримка високої продуктивності, інкапсуляція, процедурне програмування та SQL, адміністрування / підтримка / підтримка / логічність та логічне використання БД.


Як можливі всі ці чудеса, коли обоє лише інтерфейси?!?!
Рафаель

1
"Чудеса" стає можливою завдяки стандартним заводським методам, які повертають (конкретні для постачальника) реалізацію інтерфейсів: Connection.createStatement і Connection.prepareStatement. Цей дизайн змушує вас працювати проти інтерфейсів, тому вам не потрібно знати конкретні класи реалізації та уникати зайвих тісних зв’язків з такими класами реалізації. Все пояснено на прикладах у документах Java jdbc та документах Java. :)
Glen Best

Ваша частина "як правило" не має сенсу, хіба не навпаки 🤔
bhathiya-

38

PreparedStatementє дуже хорошою захистом (але не беззахисною) у запобіганні атак на ін'єкцію SQL . Прив'язка значень параметрів - хороший спосіб захиститись від "маленьких таблиць Боббі", які не бажають відвідувати.


6
Як тоді можна виконати ін'єкцію SQL через підготовлений оператор?
Майкл Боргвардт

2
Майкл, Змінні, передані як аргументи до підготовлених заяв, автоматично вийде драйвером JDBC.
CodeBee ..

3
Чи можете ви навести приклад того, як атака ін'єкції SQL буде працювати проти підготовленого оператора? Ви припускаєте помилку в коді бази даних?
Пітер Рекор

2
Так, але це далеко за межами "досить німого". Це розум дує дурість. Ніхто з унцією знань не зробив би цього.
duffymo

2
Крім того, багато постачальників баз даних не підтримують параметризацію імен стовпців (думаю ORDER BY) та / або числових констант у певних місцях (думаю LIMIT, OFFSETта інші рішення для створення сторінок), тому вони можуть бути атаковані ін'єкцією SQL, навіть коли підготовлені заяви та параметризація використовуються де завгодно можливо.
dnet

31

Деякі з переваг PreparedStatement над заявою:

  1. PreparedStatement допомагає нам у запобіганні атак ін'єкцій SQL, оскільки він автоматично уникає спеціальних символів.
  2. PreparedStatement дозволяє виконувати динамічні запити з введеннями параметрів.
  3. PreparedStatement надає різні типи методів встановлення для встановлення вхідних параметрів для запиту.
  4. PreparedStatement швидше, ніж Statement. Він стає більш помітним, коли ми повторно використовуємо PreparedStatement або використовуємо його методи пакетної обробки для виконання декількох запитів.
  5. PreparedStatement допомагає нам в написанні об'єктно-орієнтованого коду методами setter, тоді як за допомогою Statement ми повинні використовувати String Concatenation для створення запиту. Якщо потрібно встановити кілька параметрів, запит запиту за допомогою об'єднання рядків виглядає дуже некрасиво і схильний до помилок.

Детальніше про проблему ін'єкції SQL читайте на http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example


Я прочитав вашу статтю, дуже гарну. Зараз у мене запитання: чому хтось використовуватиме заяву ?! навіть для статичного запиту ?!
pedram bashiri

Я завжди використовую PreparedStatement, я не знаю жодного конкретного сценарію, де заява могла б отримати більше користі.
Панкай

13

нічого особливо додати,

1 - якщо ви хочете виконати запит у циклі (більше 1 разу), підготовлений оператор може бути швидшим через оптимізацію, про яку ви згадали.

2 - параметризований запит - хороший спосіб уникнути ін'єкції SQL. Параметризовані запити доступні лише у PreparedStatement.


10

Заява є статичною, а підготовлений показник - динамічним.

Заява підходить для DDL та підготовленої позиції для DML.

Заява повільніше, тоді як підготовлена ​​заява швидша.

більше відмінностей (заархівовано)



7

Як цитують mattjames

Використання заяви у JDBC має бути на 100% локалізовано для використання для DDL (ALTER, CREATE, GRANT тощо), оскільки це єдині типи операторів, які не можуть приймати BIND VARIABLES. PreparedStatements або CallableStatements слід використовувати для ВСІХ ІНШИХ типів операторів (DML, Queries). Оскільки це типи операторів, які приймають змінні зв'язування.

Це факт, правило, закон - використовуйте підготовлені заяви ВСЕ. Використовувати ЗАЯВЛЕННЯ майже ніде.


5

Підготовка оператора ігнорується введенням sql, тому безпека збільшується в підготовленому операторі


4
  • Простіше читати
  • Ви можете легко зробити рядок запиту постійною

4

Заява використовуватиметься для виконання статичних операторів SQL і не може приймати вхідні параметри.

PreparedStatement буде використовуватися для виконання операторів SQL багато разів динамічно. Він прийме вхідні параметри.


4

Ще одна характеристика підготовленого або параметризованого запиту: Довідка взята з цієї статті.

Цей вислів є однією з особливостей системи баз даних, в якій той самий оператор SQL виконується повторно з високою ефективністю. Підготовлені висловлювання є одним із видів Шаблону і використовуються додатком з різними параметрами.

Шаблон оператора готується та надсилається до системи баз даних, а система бази даних виконує розбір, компіляцію та оптимізацію цього шаблону та зберігає без його виконання.

Деякі з параметрів, таких як, коли пункт не передається під час створення шаблону в подальшому застосуванні, надсилає ці параметри в систему бази даних, а шаблон використання системи баз даних SQL Statement і виконується відповідно до запиту.

Підготовлені висловлювання дуже корисні проти SQL Injection, оскільки програма може готувати параметр, використовуючи різні методи та протоколи.

Коли кількість даних збільшується і в цей час індекси часто змінюються. Підготовлені заяви можуть бути невдалі, оскільки в цій ситуації потрібен новий план запитів.


3

Statement інтерфейс виконує статичні оператори SQL без параметрів

PreparedStatement інтерфейс (розширює заяву) виконує попередньо складений оператор SQL з / без параметрів

  1. Ефективний для повторних страт

  2. Він попередньо складений, тому він швидше


2

Не заплутайтеся: просто запам'ятайте

  1. Заява використовується для статичних запитів, таких як DDL, тобто створення, падіння, зміна та підготовка. Стан використовується для динамічних запитів, тобто запитів DML.
  2. У Statement запит не попередньо компілюється, тоді як запит PripravStatement є попередньою компіляцією, оскільки цей PripravStatement є економічно ефективним.
  3. ПідготовкаStatement бере аргументи під час створення, тоді як Statement не бере аргументів. Наприклад, якщо ви хочете створити таблицю та вставити елемент, то :: Створіть таблицю (статичну), використовуючи елемент Заяви та Вставте (динамічний), використовуючи PrepaStatement.

1
PrepaStatement приймає аргументи під час створення, тоді як у Заяві немає аргументів.

1

Я дотримувався всіх відповідей на це питання, щоб змінити діючий застарілий код, використовуючи - Statement(але маючи SQL Injections) на рішення, що використовує 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, я змінив цей код на

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 в цьому сценарії.

Крім того, коли ви використовуєте вбудований пакетний засіб, вам потрібно потурбуватися про закриття лише однієї заяви, але при такому підході до списку вам потрібно закрити заяву перед повторним використанням, повторно використовуючи підготовлений стан

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.