Contents | Prev | Next | Главная страница JDBCTM Guide: Getting Started


4 - Запрос (Statement)

4.1 Обзор

Объект Statement используется для выполнения SQL-запросов к БД. Существует три типа объектов Statement. Все три служат как бы конейнерами для выполнения SQL-выражений через данное соединение: Statement, PreparedStatement, наследующий от Statement, и CallableStatement, наследующий от PreparedStatement. Они специализируются на различных типах запросов: Statement используется для выполненияпростых SQL-запросов без параметров; PreparedStatement используется для выполнения прекомпилированных SQL-запросов с или без входных (IN) параметров; CallableStatement используется для вызовов хранимых процедур.

Интерфейс Statement предоставляет базовые методы для выполнения запросов и извлечения результатов. Интерфейс PreparedStatement добавляет методы управления входными (IN) параметрами; CallableStatement добавляет методы для манипуляции выходними (OUT) параметрами.

4.1.1 Создание объектов Statement

Как только соединение с определенной БД установлено, оно может использоваться для выполнения SQL-запросов. Объект 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");

4.1.2 Выполнение запроса через объект Statement

Интерфейс 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.

4.1.3 Выполнение запросов

Если соединение настроено на режим автофиксации (auto-commit), то каждый SQL-запрос либо фиксируется, либо откатывается. Оператор (statement) называется завершенным (complete), если он выполнился и все его результаты были возвращены. Для метода executeQuery, который возвращает единственный набор данных, оператор завершен, если считаны все строки соответствующего объекта ResultSet, который был возвращен методом executeXXX. В случае метода executeUpdate оператор завершен сразу после выполнения оператора. В случае вызова execute оператор остается не завершенным до тех пор, пока все наборы данных или счетчики обновлений, сгенерированные оператором, не будут считаны.

Некоторые СУБД трактуют каждый оператор в зранимой процедуре как отдельный запрос; другие трактуют их как один составной запрос. Это различие становится важным в случае использования режима автофиксации (auto-commit). В первом случае каждое выражение фиксируется отдельно, а во втором - все вместе.

4.1.4 Закрытие объектов Statement

Объекты Statement закрываются автоматически с помощью сборщика мусора виртуальной машины Java. Тем не менее рекомендуется закрывать их явно после того, как в них отпадает необходимость. Закрытие объектов Statement сразу же освобождает ресурсы СУБД и позволяет избежать проблем с памятью.

4.1.5 Подстановочный (ecape) синтаксис SQL в объектах Statement

Объекты Statement могут содержать SQL-выражения с т.н. escape-синтаксисом - синтаксисом подстановки. Escape-конструкция сигнализирует драйверу о том, что код внутри нее должен обрабатываться особо. Драйвер сканирует выражение и находит escape-последовательности, которые затем заменяются кодом, специфичным для данной СУБД. Escape-синтаксис независим от СУБД и позволяет программисту использовать возможности СУБД, которые иначе никак не доступны.

Escape-конструкция заключается в фигурные скобки и ключевое слово:

       {ключ.слово . . . параметры . . . }
Ключевое слово индицирует вид Escape-конструкции, как показано ниже:

Операция SQL LIKE использует шаблонные символы "%" и "_" ("%" соответствует нулю или более символов, "_" - ровно одному символу). Чтобы эти символы интерпретировались буквально, их надо предварить символом "\" или каким-нибудь другим (в зависимости от конкретной СУБД). Этот специальный символ и называется эскейп-символом. Можно явно задать, какой именно из символов использовать в качестве эскейп-символа, если в конце запроса ввести следующую конструкцию:

      {escape 'escape-character'}
Например, в следующем примере осуществляется поиск строки, начинающейся со знака подчеркивания:

    stmt.executeQuery("SELECT name FROM Identifiers 
            WHERE Id LIKE `\_%' {escape `\'};

Почти во всех СУБД есть скалярные функции манипуляции с числами, строками, временем, датой. Эти функции могут использоваться в 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 относительно прекомпиляции.

4.1.6     Использование метода execute

Метод 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;      // больше результатов нет



Содержание | Предыдущий | Следующий