Перейти к основному содержимому

Разница между оператором и подготовленным оператором

· 4 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим различия между интерфейсами JDBC Statement и PreparedStatement . Мы не будем рассматривать CallableStatement — интерфейс API JDBC , который используется для выполнения хранимых процедур.

2. API-интерфейс JDBC

Оба оператора и PreparedStatement могут использоваться для выполнения запросов SQL. Эти интерфейсы очень похожи. Однако они существенно отличаются друг от друга по характеристикам и производительности:

  • Оператор используется для выполнения строковых SQL- запросов.
  • PreparedStatement используется для выполнения параметризованных SQL-запросов.

Чтобы иметь возможность использовать Statement и PreparedStatement в наших примерах, мы объявим JDBC- коннектор h2 как зависимость в нашем файле pom.xml :

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>

Давайте определим сущность, которую мы будем использовать в этой статье:

public class PersonEntity {
private int id;
private String name;

// standard setters and getters
}

3. Заявление

Во-первых, интерфейс Statement принимает строки как SQL-запросы. Таким образом, код становится менее читаемым , когда мы объединяем строки 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);
}

Во- вторых, он уязвим для SQL-инъекций . Следующие примеры иллюстрируют эту слабость.

В первой строке обновление установит для столбца « имя » во всех строках значение « хакер », поскольку все, что следует после «-», интерпретируется как комментарий в SQL, а условия оператора обновления будут игнорироваться. Во второй строке вставка завершится ошибкой, потому что кавычка в столбце « имя » не экранирована:

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

В- третьих, JDBC передает запрос со встроенными значениями в базу данных . Поэтому нет никакой оптимизации запросов, а самое главное, все проверки должен обеспечивать движок базы данных . Кроме того, запрос не будет выглядеть так же в базе данных, и это предотвратит использование кеша . Точно так же пакетные обновления необходимо выполнять отдельно:

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

В- четвертых, интерфейс Statement подходит для таких DDL - запросов, как CREATE , ALTER и DROP :

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

Наконец, интерфейс Statement нельзя использовать для хранения и извлечения файлов и массивов .

4. Подготовленное заявление

Во-первых, PreparedStatement расширяет интерфейс Statement . Он имеет методы для привязки различных типов объектов , включая файлы и массивы. Следовательно, код становится понятным :

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

Во- вторых, он защищает от SQL-инъекций , экранируя текст для всех предоставленных значений параметров:

@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<PersonEntity> result = dao.getAll();
assertEquals(Arrays.asList(
new PersonEntity(1, "hacker' --"),
new PersonEntity(2, "skeet")), result);
}

В-третьих, PreparedStatement использует предварительную компиляцию . Как только база данных получит запрос, она проверит кеш перед предварительной компиляцией запроса. Следовательно, если он не кэширован, механизм базы данных сохранит его для следующего использования.

Кроме того, эта функция ускоряет обмен данными между базой данных и JVM через двоичный протокол, отличный от SQL. То есть в пакетах меньше данных, поэтому связь между серверами идет быстрее.

В-четвертых, PreparedStatement обеспечивает пакетное выполнение во время одного подключения к базе данных . Давайте посмотрим на это в действии:

public void insert(List<PersonEntity> 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();
}

Далее, PreparedStatement предоставляет простой способ хранения и извлечения файлов с использованием типов данных BLOB и CLOB . Точно так же он помогает хранить списки путем преобразования java.sql.Array в массив SQL.

Наконец, PreparedStatement реализует такие методы, как getMetadata() , которые содержат информацию о возвращаемом результате.

5. Вывод

В этом уроке мы представили основные различия между PreparedStatement и Statement . Оба интерфейса предлагают методы для выполнения SQL-запросов, но больше подходит использование Statement для запросов DDL и PreparedStatement для запросов DML.

Как обычно, все примеры кода доступны на GitHub .