Statement
используется для выполнения SQL-запросов к БД.
Существует три типа
объектов Statement
. Все три служат как бы конейнерами для выполнения
SQL-выражений через данное соединение:
Statement
,
PreparedStatement
, наследующий от
Statement
, и CallableStatement
, наследующий
от PreparedStatement
. Они специализируются на различных типах
запросов:
Statement
используется для выполненияпростых SQL-запросов без
параметров;
PreparedStatement
используется для выполнения прекомпилированных
SQL-запросов с или без входных (IN) параметров;
CallableStatement
используется для вызовов хранимых процедур.
Интерфейс Statement
предоставляет базовые методы для выполнения
запросов и извлечения результатов.
Интерфейс PreparedStatement
добавляет методы управления
входными (IN) параметрами;
CallableStatement
добавляет методы для манипуляции выходними
(OUT) параметрами.
Statement
создается методом
Connection.createStatement
, как показано ниже:
Connection con = DriverManager.getConnection(url
, "sunny", "");
Statement stmt = con.createStatement();
Посылаемое в БД SQL-выражение передается в качестве аргумента одному из методов
объекта Statement
для выполнения SQL-запроса:
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table");
Statement
предоставляет три различных метода выполнения
SQL-выражений:
executeQuery
,
executeUpdate
и execute
, в зависимости от SQL-запроса.
Метод executeQuery
необходим для запросов, результатом которых
является один единственный набор значений, таких как запросов
SELECT
.
Метод executeUpdate
используется для выполнения операторов
INSERT
, UPDATE
или DELETE
, а также
для операторов DDL (Data Definition Language - язык определения данных), например,
CREATE
TABLE
и DROP
TABLE
.
Результатом оператора INSERT
, UPDATE
,
или DELETE
является модификация одной или более колонок в нуле
или более строках таблицы. Метод
executeUpdate
возвращает целое число, показывающее, сколько строк
было модифицировано. Для выражений типа
CREATE
TABLE
и
DROP
TABLE
, которые не оперируют над строками,
возвращаемое методом
executeUpdate
значение всегда равно нулю.
Метод execute
используется, когда операторы SQL возвращают
более одного набора данных, более одного счетчика обновлений или и то, и другое.
Поскольку такая возможность редко используется программистами, она
описана чуть позже.
Все методы выполнения SQL-запросов закрывают предыдущий набор
результатов (result set) у данного объекта Statement
.
Это означает, что перед тем как выполнять следующий запрос над тем же
объектом Statement
, надо завершить обработку результатов
предыдущего (ResultSet
).
Надо заметить, что интерфейс PreparedStatement
,
наследующий все методы
Statement
, имеет свои версии методов
executeQuery
, executeUpdate
и
execute
. Объекты Statement
сами по себе
не "помнят" SQL-выражение; последнее должно быть указано в качестве аргумента
методов
Statement.executeXXX
.
Объекты PreparedStatement
не принимают SQL-выражения в виде аргументов
этих методов, так как они уже содержат прекомпилированные SQL-выражения.
Объекты типа CallableStatement
наследуют эти методы без параметров
от PreparedStatement
. Использование аргументов
в методах executeXXX объектов
PreparedStatement
и CallableStatement
приведет к генерации
ошибки SQLException
.
executeQuery
, который возвращает единственный набор данных,
оператор завершен, если считаны все строки соответствующего объекта
ResultSet
, который был возвращен методом executeXXX.
В случае метода executeUpdate
оператор завершен сразу после
выполнения оператора. В случае вызова execute
оператор остается
не завершенным до тех пор, пока все наборы данных или счетчики обновлений,
сгенерированные оператором, не будут считаны.
Некоторые СУБД трактуют каждый оператор в зранимой процедуре как отдельный запрос; другие трактуют их как один составной запрос. Это различие становится важным в случае использования режима автофиксации (auto-commit). В первом случае каждое выражение фиксируется отдельно, а во втором - все вместе.
Statement
закрываются автоматически
с помощью сборщика мусора виртуальной машины Java.
Тем не менее рекомендуется закрывать их явно после того, как в них отпадает необходимость.
Закрытие объектов Statement сразу же освобождает ресурсы СУБД и позволяет избежать
проблем с памятью.
Statement
могут содержать SQL-выражения с т.н. escape-синтаксисом -
синтаксисом подстановки. Escape-конструкция сигнализирует драйверу о том, что
код внутри нее должен обрабатываться особо. Драйвер сканирует выражение и находит
escape-последовательности, которые затем заменяются кодом, специфичным для
данной СУБД. Escape-синтаксис независим от СУБД и позволяет программисту использовать
возможности СУБД, которые иначе никак не доступны.
Escape-конструкция заключается в фигурные скобки и ключевое слово:
{ключ.слово . . . параметры . . . }Ключевое слово индицирует вид Escape-конструкции, как показано ниже:
escape
для эскейп-последовательности операции LIKE
Операция SQL
LIKE
использует шаблонные символы "%" и "_" ("%" соответствует нулю или более символов, "_" - ровно одному символу). Чтобы эти символы интерпретировались буквально, их надо предварить символом "\" или каким-нибудь другим (в зависимости от конкретной СУБД). Этот специальный символ и называется эскейп-символом. Можно явно задать, какой именно из символов использовать в качестве эскейп-символа, если в конце запроса ввести следующую конструкцию:
{escape 'escape-character'}
Например, в следующем примере осуществляется поиск строки, начинающейся со знака подчеркивания:
stmt.executeQuery("SELECT name FROM Identifiers WHERE Id LIKE `\_%' {escape `\'};
fn
для скалярных функций
Почти во всех СУБД есть скалярные функции манипуляции с числами, строками, временем, датой. Эти функции могут использоваться в escape-конструкции с ключевым словом
fn
, именем требуемой функции и ее аргументами. Следующий пример вызывает функцию конкатенацииconcat
с двумя аргументами:
{fn concat("Hot", "Java")};
Имя текущего пользователя БД может быть извлечено с помощью следующего вызова:
{fn user()};
Скалярные функции могут поддерживаться различными СУБД с немного отличающимся синтаксисом, а некоторыми СУБД могут и не поддерживаться вовсе. Различные методы классаDatabaseMetaData
возвращают список поддерживаемых функций. Например, методgetNumericFunctions
возвращает список имен числовых функций, разделенных запятой, а методgetStringFunctions
возвращает строковые функции, и т.д.Драйвер может либо отображать escape-функции в родной для данной СУБД SQL-код, либо вычислять функцию сам.
Разные СУБД отличаются способом записи даты, времени и временного штампа (timestamp, включает и дату, и время). JDBC поддерживает стандартный формат ISO для таких литералов посредством использования escape-конструкций в SQL-выражениях, которые каждым конкретным драйвером в SQL-код, специфичный для каждой конкретной СУБД.
Например, даты в JDBC записываются так:
{d `гггг-мм-дд'}
гдегггг
- это год,мм
- месяц, идд
- день. Например, драйвер заменит последовательность{d 1999-02-28}
последовательностью'28- FEB-99'
, если СУБД воспринимает именно такой формат дат.Аналогичным образом обрабатываются escape-конструкции для типов данных
TIME
иTIMESTAMP
:
{t `чч:мм:сс'} {ts `гггг-мм-дд чч:мм:сс.д . . .'}
Дробная секунда (.д
) вTIMESTAMP
может быть опущена.
Если СУБД поддерживает хранимые процедуры, то они могут вызываться из JDBC с помощью следующего синтаксиса:
{call имя_процедуры[(?, ?, . . .)]}
или, в случае, когда процедура возвращает значение:
{? = call имя_процедуры[(?, ?, . . .)]}
Квадратные скобки означают, что все, что в них заключено, можно пропустить. Они не входят в саму синтаксическую конструкцию.Входные аргументы могут быть либо литералами, либо параметрами. См. раздел 7 "CallableStatement" этого руководства.
Чтобы узнать, поддерживает данная СУБД хранимые процедуры или нет, можно вызвать
DatabaseMetaData.supportsStoredProcedures
.
Синтаксис внешних соединений следующий:
{oj внеш-соединение}
гдевнеш-соединение
задается в виде
таблица LEFT OUTER JOIN {таблица | внеш-соединение} ON условия-поиска
Внешние соединения - это продвинутая возможность SQL, которую стоит изучить (см. грамматику SQL). Чтобы можно было определять типы внешних соединений, которые поддерживает драйвер, в JDBC есть три метода классаDatabaseMetaData
:supportsOuterJoins
,supportsFullOuterJoins
иsupportsLimitedOuterJoins
.
МетодStatement.setEscapeProcessing
включает или отключает обработку escape-последовательностей. По умолчанию обработка включена. Прграммист может отключить эту возможность, если производительность имеет большое значение, но в нормальной ситуации она должна быть включена. Надо заметить, чтоsetEscapeProcessing
не работает для объектовPreparedStatement
, так как запрос уже, возможно, отослан в БД перед этим вызовом. См. API,PreparedStatement
относительно прекомпиляции.
execute
должен использоваться тогда, когда возможно
возвращение нескольких объектов
ResultSet
, более одного счетчика обновлений или комбинации
объектов ResultSet
и счетчиков обновлений. Такие множественные результаты,
хоть и редки, но возможны при вызове некоторых хранимых процедур или динамическом
вызове неизвестного на этапе компиляции SQL-запроса. Например, пользователь
может выполниь хранимую процедуру
(используя объект CallableStatement
- см.
CallableStatement), и
эта процедура может выполнить сначала обновление, потом выборку (select), потом
снова обновление, снова выборку и т.д. Обычно тот, кто использует хранимую
процедуры знает, что она возвращает.
Так как метод execute
сталкивается с неординарными случаями, то не
удивительно, что считывание его результатов требует специального подхода.
Предположим, что процедура возвращает два набора данных. После вызова
метода execute
и выполнения процедуры необходимо вызвать
метод getResultSet
для получения первого набора данных
и соответствующие методы getXXX
этого набора (ResultSet) для
извлечения из него значений. Чтобы получить следующий набор возвращенных данных
надо вызвать
getMoreResults
и затем getResultSet
второй раз.
Если известно, что процедура возвратила два счетчика обновления, то сначала
вызывается метод getUpdateCount
, потом
getMoreResults
и второй раз getUpdateCount
.
Сложнее дело обстоит в случаях, когда неизвестно, что именно возвращается.
Метод execute
возвращает true
,
если результатом является объект ResultSet
, и
false
, если int
. Если возвращается int
,
то это означает, что результат - либо счетчик обновлений, либо вополняемый
оператор - это оператор DDL. Первое, что надо сделать после вызова
execute
- это вызвать либо
getResultSet
, либо getUpdateCount
. Метод
getResultSet
вызывается, когда ожидается первый из двух или
более объектов ResultSet
; метод
getUpdateCount
- когда первый из двух или более счетчиков
обновления.
Если результат SQL-запроса - это не набор данных, то метод
getResultSet
возвратит null
.
Это означает, что либо следующим результатом является счетчик обновления,
либо больше нет результатов. Единственный способ узнать, что именно означает
этот null - это вызвать метод
getUpdateCount
, возвращающий целое число. Это число будет равно
либо числу обработанных записей, либо
-1
, если результатов больше нет.
Если метод getResultSet
уже вернул значение null
,
означающее, что результат - не объект
ResultSet
, то значение -1
должно означать, что больше
результатов не существует. Другими словами, конец последовательности результатов
наступает тогда, когда истинно следующее:
((stmt.getResultSet() == null) && (stmt.getUpdateCount() == -1))После вызова
getResultSet
и обработки возвращенного объекта
ResultSet
необходимо вызвать метод
getMoreResults
, чтобы проверить, существует ли еще один набор данных
или счетчик обновлений.
Если getMoreResults
возвращает true
, то
нужно вызвать getResultSet
снова, чтобы получить следующий набор данных.
Как уже отмечалось, если возвращенное методом getResultSet
значение окажется
null
, то, чтобы выяснить, что этот null означает (счетчик обновлений
или отсутствие результата), надо вызвать getUpdateCount
.
Значение false
, возвращенное из getMoreResults
,
означает, что следующий результат в последовательности - это счетчик обновлений
или конец последовательности. Чтобы определиться между этими вариантами,
надо вызвать
метод getUpdateCount
.
Условием конца последовательности является выражение:
((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))Пример, приведенный ниже, демонстрирует один из способов получения всех наборов данных и счетчиков обновлений, сгенерированных вызовом
execute
:
stmt.execute(queryStringWithUnknownResults
);
while (true) {
int rowCount = stmt.getUpdateCount();
if (rowCount > 0) { // это счетчик обновлений
System.out.println("Изменилось строк: " + count);
stmt.getMoreResults();
continue;
}
if (rowCount == 0) { // команда DDL или 0 обновлений
System.out.println(" Строки не менялись; либо выражение было DDL-командой");
stmt.getMoreResults();
continue;
}
// если мы до сюда дошли, то у нас либо набор данных (result set),
// либо результатов больше нет
ResultSet rs = stmt.getResultSet;
if (rs != null) {
. . . // используем метаданные для получения информации о колонках набора данных
while (rs.next()) {
. . . // обрабатываем результаты
stmt.getMoreResults();
continue;
}
break; // больше результатов нет