1. Управління поточним рядком

Першу програму ми написали, і вона чудово відпрацювала. Ми написали запит, виконали його, і в результаті метод 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

2. Отримання даних із поточного рядка

Ти навчився віртуозно керувати поточним рядком. Тепер давай розберемо, як із нього отримувати дані. Для цього в об'єкта 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()

Суть, думаю, зрозуміла.

3. Отримання різних даних про 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