Рубрики
Без рубрики

Руководство по интерфейсу набора результатов JDBC

Узнайте, как использовать API набора результатов JDBC для извлечения и обновления данных

Автор оригинала: baeldung.

1. Обзор

API подключения к базе данных Java (JDBC) обеспечивает доступ к базе данных из приложения Java. Мы можем использовать JDBC для подключения к любой базе данных, если доступен поддерживаемый драйвер JDBC.

Набор результатов представляет собой таблицу данных, сгенерированную в результате выполнения запросов к базе данных. В этом уроке мы более подробно рассмотрим Набор результатов API .

2. Создание набора результатов

Сначала мы получаем Набор результатов , вызывая ExecuteQuery() для любого объекта, реализующего Оператор интерфейс. Как Подготовленное заявление , так и Вызываемое заявление являются подинтерфейсами Заявления :

PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees");
ResultSet rs = pstmt.executeQuery();

Объект ResultSet поддерживает курсор, указывающий на текущую строку результирующего набора. Мы будем использовать next() в нашем Наборе результатов для перебора записей.

Далее мы будем использовать методы getX() при повторном просмотре результатов для извлечения значений из столбцов базы данных , где X – тип данных столбца. Фактически, мы предоставим имена столбцов базы данных методам getX() :

while(rs.next()) {
    String name = rs.getString("name");
    Integer empId = rs.getInt("emp_id");
    Double salary = rs.getDouble("salary");
    String position = rs.getString("position");
}

Аналогично, номер индекса столбца может использоваться с getX() методами вместо имени столбца. Номер индекса-это последовательность столбцов в инструкции SQL select.

Если в инструкции select не указаны имена столбцов, номер индекса-это последовательность столбцов в таблице. Нумерация индексов столбцов начинается с единицы:

Integer empId = rs.getInt(1);
String name = rs.getString(2);
String position = rs.getString(3);
Double salary = rs.getDouble(4);

3. Извлечение метаданных из набора результатов

В этом разделе мы рассмотрим, как получить информацию о свойствах и типах столбцов в наборе результатов /.

Во-первых, давайте используем метод getMetadata() в нашем наборе результатов для получения ResultSetMetaData :

ResultSetMetaData metaData = rs.getMetaData();

Далее, давайте получим количество столбцов, которые находятся в нашем наборе результатов :

Integer columnCount = metaData.getColumnCount();

Кроме того, мы можем использовать любой из приведенных ниже методов в нашем объекте метаданных для извлечения свойств каждого столбца:

  • getColumnName(int Номер столбца) для получения имени столбца
  • getColumnLabel(int ColumnNumber) для доступа к метке столбца, которая указана после КАК в SQL-запросе
  • getTableName(int Номер столбца) для получения имени таблицы, к которой принадлежит этот столбец
  • getColumnClassName(int Номер столбца) для получения типа данных Java столбца
  • getColumnTypeName(int Номер столбца) для получения типа данных столбца в базе данных
  • getColumnType(int Номер столбца) |/– для получения типа данных SQL столбца isAutoIncrement(int Номер столбца)
  • указывает, является ли столбец автоматическим приращением isCaseSensitive(int Номер столбца)
  • указывает, имеет ли значение регистр столбца isSearchable(int ColumnNumber)
  • предлагает, можем ли мы использовать столбец в предложении where SQL-запроса Валюта(номер столбца int)
  • сигнализирует, содержит ли столбец денежную стоимость IsNullable(int ColumnNumber)
  • возвращает ноль , если столбец не может быть равен нулю, один , если столбец может содержать значение null, и два , если возможность обнуления столбца неизвестна isSigned(int Номер столбца)
  • возвращает true если значения в столбце подписаны, в противном случае возвращает false

Давайте пройдемся по столбцам, чтобы получить их свойства:

for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++) {
    String catalogName = metaData.getCatalogName(columnNumber);
    String className = metaData.getColumnClassName(columnNumber);
    String label = metaData.getColumnLabel(columnNumber);
    String name = metaData.getColumnName(columnNumber);
    String typeName = metaData.getColumnTypeName(columnNumber);
    int type = metaData.getColumnType(columnNumber);
    String tableName = metaData.getTableName(columnNumber);
    String schemaName = metaData.getSchemaName(columnNumber);
    boolean isAutoIncrement = metaData.isAutoIncrement(columnNumber);
    boolean isCaseSensitive = metaData.isCaseSensitive(columnNumber);
    boolean isCurrency = metaData.isCurrency(columnNumber);
    boolean isDefiniteWritable = metaData.isDefinitelyWritable(columnNumber);
    boolean isReadOnly = metaData.isReadOnly(columnNumber);
    boolean isSearchable = metaData.isSearchable(columnNumber);
    boolean isReadable = metaData.isReadOnly(columnNumber);
    boolean isSigned = metaData.isSigned(columnNumber);
    boolean isWritable = metaData.isWritable(columnNumber);
    int nullable = metaData.isNullable(columnNumber);
}

4. Навигация по набору результатов

Когда мы получаем Результирующий набор , положение курсора находится перед первой строкой. Более того, по умолчанию Результирующий набор перемещается только в прямом направлении. Но мы можем использовать прокручиваемый Набор результатов для других вариантов навигации.

В этом разделе мы обсудим различные варианты навигации.

4.1. Типы наборов результатов

Набор результатов тип указывает, как мы будем управлять набором данных:

  • TYPE_FORWARD_ONLY – параметр по умолчанию, при котором курсор перемещается от начала до конца
  • TYPE_SCROLL_INSENSITIVE – наш курсор может перемещаться по набору данных как в прямом, так и в обратном направлениях; если при перемещении по набору данных в базовых данных происходят изменения, они игнорируются; набор данных содержит данные с момента, когда запрос базы данных возвращает результат
  • TYPE_SCROLL_ЧУВСТВИТЕЛЬНЫЙ – аналогично типу без учета прокрутки, однако для этого типа набор данных немедленно отражает любые изменения в базовых данных

Не все базы данных поддерживают все типы Результирующего набора . Итак, давайте проверим, поддерживается ли этот тип с помощью supportsResultSetType в нашем объекте DatabaseMetaData :

DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);

4.2. Прокручиваемый набор результатов

Чтобы получить прокручиваемый набор результатов , нам нужно передать некоторые дополнительные параметры при подготовке Оператора .

Например, мы получили бы прокручиваемый набор результатов , используя либо TYPE_SCROLL_ЧУВСТВИТЕЛЬНЫЙ , либо TYPE_SCROLL_ЧУВСТВИТЕЛЬНЫЙ в качестве набора результатов типа:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_INSENSITIVE,
  ResultSet.CONCUR_UPDATABLE); 
ResultSet rs = pstmt.executeQuery();

4.3. Параметры Навигации

Мы можем использовать любой из приведенных ниже вариантов в прокручиваемом наборе результатов :

  • далее() – переходит к следующей строке с текущей позиции
  • предыдущий() – переход к предыдущей строке
  • first() – переходит к первой строке набора результатов
  • last() – переходит к последней строке
  • beforeFirst() – переходит к началу; вызов next() в нашем наборе результатов после вызова этого метода возвращает первую строку из нашего набора результатов
  • afterLast() – переходит к концу; вызов previous() в нашем наборе результатов после выполнения этого метода возвращает последнюю строку из нашего набора результатов
  • относительный(int numOfRows) – переход вперед или назад от текущего положения с помощью numOfRows
  • абсолютный(int номер строки) – переходит к указанному номеру строки

Давайте рассмотрим несколько примеров:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

while (rs.next()) {
    // iterate through the results from first to last
}
rs.beforeFirst(); // jumps back to the starting point, before the first row
rs.afterLast(); // jumps to the end of resultset

rs.first(); // navigates to the first row
rs.last(); // goes to the last row

rs.absolute(2); //jumps to 2nd row

rs.relative(-1); // jumps to the previous row
rs.relative(2); // jumps forward two rows

while (rs.previous()) {
    // iterates from current row to the first row in backward direction
}

4.4. Количество строк набора результатов

Давайте используем getrows () , чтобы получить текущий номер строки нашего набора результатов .

Сначала мы перейдем к последней строке набора результатов , а затем используем getrows () , чтобы получить количество записей:

rs.last();
int rowCount = rs.getRow();

5. Обновление данных в наборе результатов

По умолчанию Результирующий набор доступен только для чтения. Однако мы можем использовать обновляемый Набор результатов для вставки, обновления и удаления строк.

5.1. Параллелизм набора результатов

Режим параллелизма указывает, может ли ваш Результирующий набор обновлять данные.

Параметр CONCURR_READ_ONLY используется по умолчанию и должен использоваться, если нам не нужно обновлять данные с помощью нашего набора результатов .

Однако, если нам нужно обновить данные в нашем наборе результатов , следует использовать параметр CONCURR_UPDATABLE .

Не все базы данных поддерживают все режимы параллелизма для всех наборов результатов типов . Поэтому нам необходимо проверить, поддерживается ли желаемый тип и режим параллелизма с помощью метода supportsResultSetConcurrency() :

DatabaseMetaData dbmd = dbConnection.getMetaData(); 
boolean isSupported = dbmd.supportsResultSetConcurrency(
  ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

5.2. Получение обновляемого набора результатов

Чтобы получить обновляемый Набор результатов , нам необходимо передать дополнительный параметр при подготовке Оператора . Для этого давайте использовать CONCURR_UPDATABLE в качестве третьего параметра при создании инструкции:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

5.3. Обновление строки

В этом разделе мы обновим строку, используя обновляемый Набор результатов , созданный в предыдущем разделе.

Мы можем обновить данные в строке, вызвав updates() методы, передав имена столбцов и значения для обновления. Мы можем использовать любой поддерживаемый тип данных вместо X в методе update () .

Давайте обновим столбец “зарплата” , который имеет тип двойной :

rs.updateDouble("salary", 1100.0);

Обратите внимание , что это просто обновляет данные в наборе результатов , но изменения еще не сохранены обратно в базу данных.

Наконец, давайте вызовем updateRow () , чтобы сохранить обновления в базе данных :

rs.updateRow();

Вместо имен столбцов мы можем передать индекс столбца в методы updateX () . Это похоже на использование индекса столбца для получения значений с помощью методов getX () . Передача имени столбца или индекса в методы update() дает тот же результат:

rs.updateDouble(4, 1100.0);
rs.updateRow();

5.4. Вставка строки

Теперь давайте вставим новую строку, используя наш обновляемый Набор результатов .

Сначала мы будем использовать moveToInsertRow() для перемещения курсора для вставки новой строки:

rs.moveToInsertRow();

Затем мы должны вызвать update() методы, чтобы добавить информацию в строку. Нам нужно предоставить данные для всех столбцов в таблице базы данных. Если мы не предоставляем данные для каждого столбца, то используется значение столбца по умолчанию:

rs.updateString("name", "Venkat"); 
rs.updateString("position", "DBA"); 
rs.updateDouble("salary", 925.0);

Затем давайте вызовем insertRow () , чтобы вставить новую строку в базу данных:

rs.insertRow();

Наконец, давайте воспользуемся moveToCurrentRow(). Это вернет положение курсора в строку, в которой мы находились до того, как начали вставлять новую строку с помощью метода moveToInsertRow() :

rs.moveToCurrentRow();

5.5. Удаление строки

В этом разделе мы удалим строку, используя наш обновляемый Набор результатов .

Сначала мы перейдем к строке, которую хотим удалить. Затем мы вызовем метод deleteRow () , чтобы удалить текущую строку:

rs.absolute(2);
rs.deleteRow();

6. Возможность хранения

Возможность хранения определяет, будет ли наш Набор результатов открыт или закрыт в конце транзакции базы данных.

6.1. Типы Удерживаемости

Используйте CLOSE_CURSORS_AT_COMMIT , если Набор результатов не требуется после фиксации транзакции.

Используйте HOLD_CURSORS_OVER_COMMIT для создания удерживаемого набора результатов . Удерживаемый Результирующий набор не закрывается даже после фиксации транзакции базы данных.

Не все базы данных поддерживают все типы хранения.

Итак, давайте проверим, поддерживается ли тип удерживаемости с помощью supportsResultSetHoldability() на нашем объекте DatabaseMetaData . Затем мы получим сохраняемость базы данных по умолчанию с помощью getResultSetHoldability() :

boolean isCloseCursorSupported
  = dbmd.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
boolean isOpenCursorSupported
  = dbmd.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
boolean defaultHoldability
  = dbmd.getResultSetHoldability();

6.2. Удерживаемый набор результатов

Чтобы создать удерживаемый набор результатов , нам нужно указать тип удерживаемость в качестве последнего параметра при создании Оператора . Этот параметр указывается после режима параллелизма.

Обратите внимание, что если мы используем Microsoft SQL Server (MSSQL), мы должны установить возможность хранения в соединении с базой данных, а не в наборе результатов :

dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);

Давайте посмотрим на это в действии. Во-первых, давайте создадим Оператор , установив возможность удержания в HOLD_CURSORS_OVER_COMMIT :

Statement pstmt = dbConnection.createStatement(
  ResultSet.TYPE_SCROLL_SENSITIVE, 
  ResultSet.CONCUR_UPDATABLE, 
  ResultSet.HOLD_CURSORS_OVER_COMMIT)

Теперь давайте обновим строку при извлечении данных. Это похоже на пример обновления, который мы обсуждали ранее, за исключением того, что мы продолжим повторять Набор результатов после фиксации транзакции обновления в базе данных. Это отлично работает как с базами данных MySQL, так и с базами данных MSSQL:

dbConnection.setAutoCommit(false);
ResultSet rs = pstmt.executeQuery("select * from employees");
while (rs.next()) {
    if(rs.getString("name").equalsIgnoreCase("john")) {
        rs.updateString("name", "John Doe");
        rs.updateRow();
        dbConnection.commit();
    }                
}
rs.last();

Стоит отметить, что MySQL поддерживает только HOLD_CURSORS_OVER_COMMIT . Таким образом, даже если мы используем CLOSE_CURSORS_AT_COMMIT , это будет проигнорировано.

База данных MSSQL поддерживает CLOSE_CURSORS_AT_COMMIT . Это означает, что Результирующий набор будет закрыт, когда мы совершим транзакцию. В результате попытка доступа к набору результатов после фиксации транзакции приводит к ошибке “Курсор не открыт”. Поэтому мы не можем получить дополнительные записи из набора результатов .

7. Размер Выборки

Как правило, при загрузке данных в Результирующий набор драйверы базы данных принимают решение о количестве строк , которые необходимо извлечь из базы данных. Например, в базе данных MySQL Результирующий набор обычно загружает все записи в память сразу.

Однако иногда нам может потребоваться иметь дело с большим количеством записей, которые не помещаются в нашу память JVM. В этом случае мы можем использовать свойство fetch size либо для нашего оператора , либо набора результатов объектов, чтобы ограничить количество первоначально возвращаемых записей.

Всякий раз, когда требуются дополнительные результаты, Результирующий набор извлекает другую партию записей из базы данных. Используя свойство fetch size, мы можем предоставить драйверу базы данных предложение о количестве строк, которые необходимо извлечь для каждой поездки в базу данных . Указанный нами размер выборки будет применен к последующим обращениям к базе данных.

Если мы не указываем размер выборки для вашего Результирующего набора , то используется размер выборки оператора |. Если мы не указываем размер выборки ни для Оператора , ни для Результирующего набора , то используется база данных по умолчанию.

7.1. Использование инструкции Fetch Size on

Теперь давайте посмотрим размер выборки в Инструкции в действии. Мы установим размер выборки Оператора равным 10 записям. Если наш запрос вернет 100 записей, то будет 10 обходов базы данных, каждый раз загружая по 10 записей:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees", 
  ResultSet.TYPE_SCROLL_SENSITIVE, 
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

while (rs.next()) {
    // iterate through the resultset
}

7.2. Использование размера выборки в наборе результатов

Теперь давайте изменим размер выборки в нашем предыдущем примере, используя Набор результатов .

Во-первых, мы будем использовать размер выборки в нашем Заявлении . Это позволяет нашему Результирующему набору изначально загружать 10 записей после выполнения запроса.

Затем мы изменим размер выборки в Результирующем наборе . Это переопределит размер выборки, который мы ранее указали в нашем Заявлении . Таким образом, все последующие поездки будут загружать 20 записей, пока все записи не будут загружены.

В результате для загрузки всех записей потребуется всего 6 поездок по базе данных:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees", 
  ResultSet.TYPE_SCROLL_SENSITIVE, 
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();
 
rs.setFetchSize(20); 

while (rs.next()) { 
    // iterate through the resultset 
}

Наконец, мы увидим, как изменить размер выборки Результирующего набора во время итерации результатов.

Как и в предыдущем примере, мы сначала установим размер выборки равным 10 в нашей инструкции |. Таким образом, наши первые 3 поездки в базу данных будут загружать по 10 записей за каждую поездку.

А затем мы изменим размер выборки в вашем Наборе результатов до 20 при чтении 30-й записи. Таким образом, следующие 4 поездки будут загружать по 20 записей за каждую поездку.

Таким образом, нам потребуется 7 поездок в базу данных, чтобы загрузить все 100 записей:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees", 
  ResultSet.TYPE_SCROLL_SENSITIVE, 
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

int rowCount = 0;

while (rs.next()) { 
    // iterate through the resultset 
    if (rowCount == 30) {
        rs.setFetchSize(20); 
    }
    rowCount++;
}

8. Заключение

В этой статье мы рассмотрели, как использовать API ResultSet для извлечения и обновления данных из базы данных. Некоторые из рассмотренных нами дополнительных функций зависят от используемой нами базы данных. Таким образом, нам необходимо проверить поддержку этих функций перед их использованием.

Как всегда, код доступен на GitHub .