PreparedStatement
наследует от Statement
и отличается от последнего следующим:
PreparedStatement
"помнят" скомпилированные SQL-выражения.
Именно поэтому они называются "prepared" ("подготовленные").
PreparedStatement
могут иметь один или более
входной (IN) параметр. Входной параметр - это параметр, чье значение не указывается
при создании SQL-выражения. Вместо него в выражении на месте каждого
входного параметра ставится знак ("?"). Значение каждого вопросительного знака
устанавливается методами
setXXX
перед выполнением запроса.
PreparedStatement
прекомпилированны,
исполнение этих запросов может происходить несколько быстрее, чем в объектах
Statement
.
В результате SQL-выражения, которые исполняются часто, в целях улучшения производительности
создают в виде объектов PreparedStatement
.
Оставаясь подклассом класса Statement
, класс
PreparedStatement
наследует все функции от
Statement
. В дополнении к ним он добавляет методы установки
входных параметров. Кроме того, три метода -
execute
, executeQuery
и executeUpdate
- модифицированы таким образом, что не имеют аргументов. Старые методы класса
Statement
(которые принимают SQL-выражения в качестве едиственного аргумента)
не должны использоваться в объекте PreparedStatement
.
con
- это объект
Connection
, создает объект PreparedStatement
,
содержащий SQL-выражение с двумя параметрами:
PreparedStatement pstmt = con.prepareStatement( "UPDATE table4 SET m = ? WHERE x = ?");Объект
pstmt
отныне содержит выражение
"UPDATE table4 SET m = ? WHERE x = ?"
,
которое уже отослано в СУБД и подготовлено для выполнения.
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(); }
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
сделает приложение более переносимым.
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
.
setNull
позволяет отсылать значения NULL
в БД
как входные параметры. Хотя JDBC-тип параметра все же можно задать явно.
JDBC-значение NULL
будет отосланов БД также в том случае, если
методу
setXXX
будет передано Java-значение null
(если
метод принимает Java-объект в качестве аргумента). Тем не менее,
метод
setObject
может принять значение null
только в случае,
если задан JDBC-тип.
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
.