«1. Обзор

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

2. Интерфейс API JDBC

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

    Оператор — используется для выполнения строковых SQL-запросов. PreparedStatement — используется для выполнения параметризованных SQL-запросов. В наших примерах мы объявим коннектор JDBC h2 как зависимость в нашем файле pom.xml:

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

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

3. Заявление

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

    // standard setters and getters
}

Во-первых, интерфейс Statement принимает строки как SQL-запросы. Таким образом, когда мы объединяем строки 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 и условия оператор обновления будет проигнорирован. Во второй строке вставка завершится ошибкой, поскольку кавычка в столбце «имя» не экранирована:

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

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

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

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

4. PreparedStatement

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

Finally, the Statement interface can’t be used for storing and retrieving files and arrays.

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

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

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

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

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

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

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

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

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 реализует такие методы, как getMetadata(), которые содержат информацию о возвращаемом результате.

5. Заключение

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

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

«