W3docs

Java JDBC ResultSet

Перебирайте строки SQL-запроса в Java с помощью интерфейса ResultSet.

ResultSet — это курсор по строкам, возвращённым запросом. Он не держит все строки в памяти одновременно: он указывает на одну строку за раз, и вы перемещаетесь по ним вперёд. Важно: свежий ResultSet расположен перед первой строкой — нужно один раз вызвать next(), чтобы встать на неё. Именно поэтому каждый цикл чтения имеет вид while (rs.next()).

ResultSet возвращается в результате запроса, выполненного через Statement или PreparedStatement. На этой странице рассматривается, как перемещать этот курсор и безопасно читать столбцы.

Цикл чтения

String sql = "SELECT id, name, score FROM player ORDER BY score DESC";
try (Statement st = conn.createStatement();
     ResultSet rs = st.executeQuery(sql)) {
  while (rs.next()) {                       // advance; false when no more rows
    int id = rs.getInt("id");               // read by column name...
    String name = rs.getString(2);          // ...or by 1-based index
    int score = rs.getInt("score");
    System.out.println(id + " " + name + " " + score);
  }
}

Чтение столбцов: по имени или по индексу

Оба варианта работают. Имена столбцов более понятны и не ломаются при изменении порядка в SELECT; индексы столбцов (1-based, как всё в JDBC) чуть быстрее. Имена почти всегда выигрывают по поддерживаемости кода. Если во время выполнения нужны имена, типы или количество столбцов — например, для отрисовки универсальной таблицы — прочитайте их из ResultSetMetaData, которые получите с помощью rs.getMetaData().

Проблема NULL и wasNull()

Геттеры примитивных типов не могут возвращать null. getInt возвращает 0 для SQL NULL, getDouble0.0 и т.д. — что неотличимо от реального нуля. Когда столбец допускает NULL и разница важна, вызовите rs.wasNull() сразу после геттера:

int score = rs.getInt("score");
if (rs.wasNull()) { /* it was SQL NULL, not a zero */ }

(Для объектных типов getObject возвращает null напрямую — это нередко удобнее.)

Тип курсора и параллельный доступ

По умолчанию ResultSet имеет тип TYPE_FORWARD_ONLY и CONCUR_READ_ONLY — можно двигаться только вперёд и только читать. Запросите TYPE_SCROLL_INSENSITIVE, чтобы использовать previous(), absolute(n) или first(), либо CONCUR_UPDATABLE, чтобы редактировать строки на месте. Это стоит дороже, поэтому запрашивайте их только при необходимости.

Закрытие (try-with-resources делает это)

ResultSet держит серверный курсор; если оставить его открытым, это расходует ресурсы базы данных. Закрытие Statement закрывает и его, а try-with-resources обрабатывает и то, и другое. Никогда не возвращайте открытый ResultSet из метода, чей Connection уже закрыт.

Практический пример: однонаправленный курсор и wasNull

Эта программа моделирует курсор ResultSet с помощью небольшого списка в памяти — стартует перед первой строкой, перемещается вперёд шагом в стиле next() — и воспроизводит поведение getInt-возвращает-0-при-NULL с проверкой wasNull, а также константы курсора.

java— editable, runs on the server

Что следует вынести из запуска:

  • Курсор начинается с индекса -1перед первой строкой — а шаг ++cursor воспроизводит rs.next(): сначала продвинуться, потом читать. Именно поэтому настоящий цикл — это while (rs.next()), и нельзя читать столбец до первого вызова next().
  • Строки читаются по одной, а не загружаются списком. Модель использует список для простоты, но настоящий ResultSet получает строки с сервера потоком — именно это позволяет ему обрабатывать наборы результатов, значительно превышающие объём памяти.
  • Оценка null у Линуса напечаталась как 0 — та самая ловушка getInt. Без флага нельзя отличить отсутствующую оценку от настоящего нуля.
  • Маркер (wasNull) — это средство различения. В настоящем JDBC вы вызываете rs.wasNull() сразу после геттера, потому что он сообщает о последнем прочитанном столбце — прочитайте другой столбец сначала, и ответ изменится.
  • Константы (TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, FETCH_FORWARD) описывают курсор по умолчанию — самый дешёвый: двигаться вперёд, только читать. Прокручиваемые или обновляемые курсоры нужно запрашивать явно, так как они требуют от сервера дополнительной работы.

Практика

Практика
Запрос читает nullable-столбец 'score' с помощью rs.getInt('score') и получает 0. Как определить, было ли значение настоящим 0 или SQL NULL?
Запрос читает nullable-столбец 'score' с помощью rs.getInt('score') и получает 0. Как определить, было ли значение настоящим 0 или SQL NULL?
Was this page helpful?