«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);
}
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.
«