Управление текущей строкой

Первую программу мы написали и она отлично отработала. Мы написали запрос, выполнили его, и в результате метод executeQuery() вернул нам объект ResultSet, который содержит все результаты запроса. И теперь мы попробуем разобраться, как эти результаты из него получить.

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

Но начнем мы с самого простого — с понятия “текущей строки результата”. Так как строк у результата обычно очень много, то объект ResultSet имеет у себя внутри указатель на текущую строку. И последовательно переключает строки для их чтения с помощью метода next().

Такой подход в первую очередь сделан для оптимизации. JDBC Driver может не загружать строки из базы, пока ты последовательно не дойдешь до их чтения. FileInputStream ты тоже читаешь последовательно с начала и до конца. Так что такой подход должен быть тебе знаком и понятен.

Однако же, если тебе очень нужно, то файлы можно читать в любом месте с помощью класса RandomAccessFile.

Класс ResultSet тоже позволяет что-то подобное и позволяет двигать текущую строку по результату куда угодно. Для этого у него есть такие методы:

Метод Описание
1 next() Переключиться на следующую строку
2 previous() Переключиться на предыдущую строку
3 isFirst() Текущая строка первая?
4 isBeforeFirst() Мы перед первой строкой?
5 isLast() Текущая строка последняя?
6 isAfterLast() Мы после последней строки?
7 absolute(int n) Делает N-ю строку текущей
8 relative(int n) Двигает текущую строку на N позиций вперед. N может быть <0
9 getRow() Возвращает номер строки

Методы достаточно простые, однако нужно сделать два пояснения. Результаты как бы обрамлены пустыми строками с обоих сторон. Поэтому изначально текущая строка находится перед первой строкой результата. И чтобы получить первую строку, нужно хотя бы раз вызвать метод next().

Если ты на последней стоке вызвал метод next(), то ты перешел на строку после последней. Данных из нее прочитать ты не можешь, но никакой ошибки не произойдет. Тут метод isAfterLast() будет возвещать true в качестве результата.

Пример:

Statement statement = connection.createStatement();
ResultSet results = statement.executeQuery("SELECT * FROM user");

System.out.println( results.getRow() );        	// 0
System.out.println( results.isBeforeFirst() );  // true
System.out.println( results.isFirst() );          	// false

results.next();

System.out.println( results.getRow() );        	// 1
System.out.println( results.isBeforeFirst() );  // false
System.out.println( results.isFirst() );          	// true

results.next();

System.out.println( results.getRow() );        	// 2
System.out.println( results.isBeforeFirst() );  // false
System.out.println( results.isFirst() );          	// false

Получение данных из текущей строки

Ты научился виртуозно управлять текущей строкой. Теперь давай разберем, как из нее получать данные. Для этого у объекта ResultSet есть специальные методы, которые все можно описать одним шаблоном:

getType(номерКолонки)

Впрочем, если у колонки есть имя, то можно получать и по имени колонки:

getType(имяКолонки)

Пример:

while (results.next()) {
        	Integer id = results.getInt(“id”);
        	String name = results.getString(“name”);
        	System.out.println(results.getRow() + ". " + id + "\t"+ name);
}

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

SQL Datatype getXXX() Methods
CHAR getString()
VARCHAR getString()
INT getInt()
FLOAT getDouble()
CLOB getClob()
BLOB getBlob()
DATE getDate()
TIME getTime()
TIMESTAMP getTimestamp()

Суть, думаю, ты понял.

Получение разных данных о ResultSet

Как читать данные из текущей строки мы разобрались: и по номеру колонки, и по ее имени. Кстати, а как узнать имя колонки по ее номеру? Или количество колонок в результате?

С одной стороны, если ты пишешь запрос, то ты вроде бы должен все это знать. С другой стороны, мы можем писать программу, которая отображает результат запроса на экран: запрос нам передают и мы хотим просто отобразить на экране (в консоли, веб-странице) все, что нам вернул SQL-сервер.

Для этого у JDBC есть специальный объект – интерфейс ResultSetMetaData. Получить объект этого типа достаточно просто:

Statement statement = connection.createStatement();
ResultSet results = statement.executeQuery("SELECT * FROM user");
ResultSetMetaData resultSetMetaData = results.getMetaData();

У интерфейса ResultSetMetaData есть очень интересные методы. Ниже приведу самые популярные из них:

1 getColumnCount() Возвращает количество колонок результата
2 getColumnName(int column) Возвращает имя колонки
3 getColumnLabel(int column) Возвращает description колонки
4 getColumnType() Возвращает тип колонки: число (специальный код)
5 getColumnTypeName() Возвращает тип колонки: строка
6 getColumnClassName() Возвращает имя java-класса для типа колонки
7 getTableName() Возвращает имя таблицы
8 getCatalogName() Возвращает имя каталога колонки
9 getSchemaName() Возвращает имя схемы базы данных
10 isAutoIncrement(int column) Колонка поддерживает AUTO INCREMENT?
11 isNullable() Колонка может содержать NULL?

Давай с его помощью немного узнаем о нашей таблице:

ResultSetMetaData metaData = results.getMetaData();
int columnCount = metaData.getColumnCount();
for (int column = 1; column <= columnCount; column++)
{
        	String name = metaData.getColumnName(column);
        	String className = metaData.getColumnClassName(column);
        	String typeName = metaData.getColumnTypeName(column);
        	int type = metaData.getColumnType(column);

        	System.out.println(name + "\t" + className + "\t" + typeName + "\t" + type);
}

Важно! Обрати внимание, что колонки нумеруются с 1. Строки, кстати, тоже. Как это необычно, да?

И вот какой результат я получил после запуска программы:

"C:\Program Files\Java\jdk-17.0.3.1\bin\java.exe…
idjava.lang.IntegerINT4
namejava.lang.StringVARCHAR12
leveljava.lang.IntegerINT4
created_datejava.sql.DateDATE91
Process finished with exit code 0