6 PreparedStatement
6.1 Введение
ИнтерфейсPreparedStatement наследует от Statement
и отличается от последнего следующим:
- Экземпляры
PreparedStatement"помнят" скомпилированные SQL-выражения. Именно поэтому они называются "prepared" ("подготовленные"). - SQL-выражения в
PreparedStatementмогут иметь один или более входной (IN) параметр. Входной параметр - это параметр, чье значение не указывается при создании SQL-выражения. Вместо него в выражении на месте каждого входного параметра ставится знак ("?"). Значение каждого вопросительного знака устанавливается методамиsetXXXперед выполнением запроса.
PreparedStatement прекомпилированны,
исполнение этих запросов может происходить несколько быстрее, чем в объектах
Statement.
В результате SQL-выражения, которые исполняются часто, в целях улучшения производительности
создают в виде объектов PreparedStatement.
Оставаясь подклассом класса Statement, класс
PreparedStatement наследует все функции от
Statement. В дополнении к ним он добавляет методы установки
входных параметров. Кроме того, три метода -
execute, executeQuery и executeUpdate
- модифицированы таким образом, что не имеют аргументов. Старые методы класса
Statement (которые принимают SQL-выражения в качестве едиственного аргумента)
не должны использоваться в объекте PreparedStatement.
6.1.1 Создание объектов PreparedStatement
Следующий фрагмент кода, гдеcon - это объект
Connection, создает объект PreparedStatement,
содержащий SQL-выражение с двумя параметрами:
PreparedStatement pstmt = con.prepareStatement( "UPDATE table4 SET m = ? WHERE x = ?");Объект
pstmt отныне содержит выражение
"UPDATE table4 SET m = ? WHERE x = ?",
которое уже отослано в СУБД и подготовлено для выполнения.
6.1.2 Передача входных (IN) параметров
Перед выполнением объектаPreparedStatement надо установить
значения всех его параметров. Это делается с помощью методов
setXXX, где
XXX - это тип параметра. Например, если параметр имеет Java-тип
long, используемый метод будет setLong.
Первый аргумент методов setXXX - это порядковый номер параметра,
а второй - значение, в которое надо его установить. Например, следующий код
устанавливает первый параметр в значение
123456789, а второй - в 100000000:
pstmt.setLong(1, 123456789); pstmt.setLong(2, 100000000);После установки параметра его можно использовать при многократном выполнении выражения до тех пор, пока он не очистится методом
clearParameters.
В режиме соединения по умолчанию (разрешена автофиксация) каждый запрос фиксируется или откатывается автоматически.
Один и тот же объект PreparedStatement может выполняться много раз,
если нижестоящий драйвер или СУБД будут сохранять выражение (statement) в открытом
состоянии даже после того как произойдет фиксация. Иначе не имеет смысла
пытаться улучшить производительность заменой Statement
на PreparedStatement.
Используя pstmt из предыдущего примера, следующий код
устанавливает значения обоих параметров и выполняет
pstmt 10 раз. Как уже было отмечено, БД не должна закрывать
pstmt. В этомпримере первый параметр устанавливается в
"Hi" и остается неизменным. Второй параметр устанавливается в
последовательные целые значения, начиная от 0 и заканчивая 9.
pstmt.setString(1, "Hi"); for (int i = 0; i < 10; i++) { pstmt.setInt(2, i); int rowCount = pstmt.executeUpdate(); }
6.1.3 Совместимость типов данных входных параметров
XXX в начвании методов setXXX - это тип данных Java.
Неявно он же является и типом данных JDBC (SQL), так как драйвер отображает
тип данных Java на соответствующий JDBC-тип (согласно таблице из раздела
8.6.2) перед отсылкой JDBC-типа в БД.
Следующий код устанавливает второй параметр объекта
PreparedStatement pstmt в 44 с типом данных
short:
pstmt.setShort(2, 44);Драйвер отошлет 44 в БД в виде типа JDBC
SMALLINT, который является
стандартным для Java-типа short.
На программисте лежит ответственность за совместимость Java-типов входных
параметров и ожидаемых базой данных соответствующих JDBC-типов. Рассмотрим
случай, когда ожидается SMALLINT. Если используется метод setByte,
то драйвер отошлет значение TINYINT в БД. Это, вероятно,
работать будет, так как многие БД преобразуют "похожие" типы данных друг в друга.
Тем не менее, чтобы приложение могдо работать как можно с большим количеством
СУБД, лучше использовать Java-типы, которые в точности соответствуют ожидаемым
JDBC-типам. Если ожидается JDBC-тип данных SMALLINT, то
использование именно setShort вместо
setByte сделает приложение более переносимым.
6.1.4 Использование объектов
Программист может явно задать конвертирование входных параметров в определенный JDBC-тип с помощью методаsetObject. Этот метод может принимать
третий аргумент, указывающий целевой JDBC-тип данных. Перед отправкой в БД
драйвер преобразует Object в указанный JDBC-тип.
Если JDBC-тип не задан, то драйвер просто преобразует
Object в его ближайший JDBC-эквивалент в соответствии с таблицей
из раздела
8.6.4, а затем пошлет его в БД. Это подобно
использованию обычных методов setXXX; в обоих случаях драйвер
отображает Java-типы в JDBC-типы данных перед отправкой значений в БД. Разница
заключается в том, что если методы setXXX используют таблцу
отображения Java-типов в JDBC-типы из раздела 8.6.2),
то метод setObject использует отображение из таблицы в разделе
8.6.4.
В случае использования метода setObject тип данных может быть
неизвестен приложения на этапе его компиляции. Используя его, приложение
может подавать на вход значения любых типов данных и преобразовывать их
в ожидаемый базой данных JDBC-тип.
Таблица из раздела 8.6.5 показывает
все возможные преобразования, которые может проделать setObject.
6.1.5 Отсылка значений NULL в качестве входного параметра
МетодsetNull позволяет отсылать значения NULL в БД
как входные параметры. Хотя JDBC-тип параметра все же можно задать явно.
JDBC-значение NULL будет отосланов БД также в том случае, если
методу
setXXX будет передано Java-значение null (если
метод принимает Java-объект в качестве аргумента). Тем не менее,
метод
setObject может принять значение null только в случае,
если задан JDBC-тип.
6.1.6 Отправка очень больших входных параметров
МетодыsetBytes и setString могут отсылать неограниченное
количество данных. Правда, иногда программисту легче передавать большие значения
в виде маленьких кусков. Это делается установкой входного параметра
в значение Java-потока ввода (input stream). Когда выполняется выражение, JDBC-драйвер
будет производить последовательные вызовы из этого потока ввода, считывая его содержимое
и пересылая его в виде значения параметра.
JDBC предоставляет три метода установки входных параметров в поток ввода:
setBinaryStream для потоков, содержащих обычные байты,
setAsciiStream для потоков ASCII-символов и
setUnicodeStream для потоков Unicode-символов.
Эти методы, в отличие от остальных методов
setXXX, принимают дополнительный аргумент, равный количеству передаваемых
в потоке байтов. Этот аргумент необходим, так как некоторые СУБД требуют
указания размера данных перед их отсылкой.
Следующий код иллюстрирует отсылку файла в виде входного параметра запроса:
java.io.File file = new java.io.File("/tmp/data"); int fileLength = file.length(); java.io.InputStream fin = new java.io.FileInputStream(file); java.sql.PreparedStatement pstmt = con.prepareStatement( "UPDATE Table5 SET stuff = ? WHERE index = 4"); pstmt.setBinaryStream (1, fin, fileLength); pstmt.executeUpdate();При выполнении запроса для доставки данных последовательно считывается поток ввода
fin.
jCenter