Зарегистрируйтесь, чтобы продолжить обучение

Общие принципы работы Java JDBC: Работа с базой данных

В этом курсе мы погрузимся в различные элементы JDBC, но сначала рассмотрим общие принципы, которые работают одинаково для любых запросов. Эти принципы включают такие пункты:

  • Установка зависимостей
  • Подключение к базе данных
  • Подготовка запроса
  • Выполнение запроса
  • Формирование результата

В этом курсе мы будем работать с базой данных H2. Она полноценно работает с SQL, но есть одно отличие — ее можно создавать только в памяти, что удобно для обучения и тестирования. При необходимости вы с легкостью можете заменить ее на любую полнофункциональную базу.

Подключение базы H2 выполняется одной строчкой:

implementation("com.h2database:h2:2.2.220")

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

package io.hexlet;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Application {
    // Нужно указывать базовое исключение,
    // потому что выполнение запросов может привести к исключениям
    public static void main(String[] args) throws SQLException {
        // Создаем соединение с базой в памяти
        // База создается прямо во время выполнения этой строчки
        // Здесь mem означает, что подключение происходит к базе данных в памяти,
        // а hexlet_test — это имя базы данных
        var conn = DriverManager.getConnection("jdbc:h2:mem:hexlet_test");

        var sql = "CREATE TABLE users (id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(255), phone VARCHAR(255))";
        // Чтобы выполнить запрос, создадим объект statement
        var statement = conn.createStatement();
        statement.execute(sql);
        statement.close(); // В конце закрываем

        var sql2 = "INSERT INTO users (username, phone) VALUES ('tommy', '123456789')";
        var statement2 = conn.createStatement();
        statement2.executeUpdate(sql2);
        statement2.close();

        var sql3 = "SELECT * FROM users";
        var statement3 = conn.createStatement();
        // Здесь вы видите указатель на набор данных в памяти СУБД
        var resultSet = statement3.executeQuery(sql3);
        // Набор данных — это итератор
        // Мы перемещаемся по нему с помощью next() и каждый раз получаем новые значения
        while (resultSet.next()) {
            System.out.println(resultSet.getString("username"));
            System.out.println(resultSet.getString("phone"));
        }
        statement3.close();

        // Закрываем соединение
        conn.close();
    }
}

Исключения

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

// Локальные редакторы могут либо автоматически помечать такие методы,
// либо подсказывать методы, которые нужно пометить
public static void main(String[] args) throws SQLException {

Соединение с базой данных

Обычно программисты делают так: рядом со своим приложением они поднимают СУБД, внутри которой они заранее создали необходимую базу данных. Таким образом, приложение соединяется с СУБД и подключается к конкретной базе данных внутри. Для этого нужны параметры подключения:

  • IP-адрес или DNS-адрес
  • Порт для подключения
  • Логин и пароль
  • Имя базы данных

В нашем примере все проще. База H2 запускается прямо в памяти нашего приложения, поэтому ей не нужны доступы. Эту базу не нужно создавать заранее, она создается в момент выполнения соединения:

var connection = DriverManager.getConnection("jdbc:h2:mem:hexlet_test");

Дальше мы можем работать с базой H2 с помощью SQL.

Здесь все как с обычной реляционной базой данных. Но важно помнить, что эта база существует, только когда приложение запущено. Если мы остановим или перезапустим приложение, это приведет к потере данных. Это нормально для учебных и тестовых задач, но не подходит для реальных приложений, поэтому в них база H2 не используется.

Стейтмент

В коде выше перед выполнением запроса мы создали стейтмент, а затем закрыли его. Далее мы обсудим, какую роль стейтмент играет в этом процессе, но сначала рассмотрим такой код:

var statement = connection.createStatement();
statement.execute(sql);
statement.close();

Выполнение запроса в базу данных — это более сложная операция, чем кажется на первый взгляд.

Для примера представим, что мы делаем запрос на выборку данных. В этом случае мы вручную пересылаем выборку из базы в приложение, потому что база передает данные только по запросу. Почему это не происходит автоматически? Дело в том, что выборка может быть огромной, тогда автоматическая пересылка привела бы к резкому скачку использования оперативной памяти.

У этой особенности есть неочевидная обратная сторона — дополнительная память начинает активно использоваться внутри самой базы данных.

Чтобы этого не происходило, мы должны четко обозначить отрезок времени, в который СУБД должна хранить запрошенные данные. Именно по этой причине нам приходится закрывать стейтменты. Когда мы это делаем, JDBC посылает сигнал базе данных, обозначая, что данные больше не нужны. В ответ на этот сигнал, база данных освобождает ресурсы.

Может показаться, что мы переложили проблему с клиента на сервер, но это не совсем так. Базы данных стараются максимально оптимизировать работу с данными, поэтому они затрачивают меньше ресурсов на хранение выборок ниже. К тому же, передача данных по сети — это долго и дорого.

А что будет, если не закрыть стейтмент? Стейтменты удерживают ресурсы системы — если забывать их закрывать, то в итоге это приведет к сбоям в работе приложения.

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

  • Запросы на выборку данных выполняются через метод stmt.executeQuery()
  • Запросы на вставку и обновление данных работают через метод stmt.executeUpdate()
  • Все остальные запросы через метод stmt.execute() — в нашем примере это создание таблицы

Объекты класса ResultSet

Последний элемент нашего примера — это ResultSet:

var sql3 = "SELECT * FROM users";
var statement3 = connection.createStatement();
var resultSet = statement3.executeQuery(sql3);
while (resultSet.next()) {
    System.out.println(resultSet.getLong("id"));
    System.out.println(resultSet.getString("username"));
    System.out.println(resultSet.getString("phone"));
}
statement3.close();

Объекты этого класса выполняют роль курсора — указателя на набор данных, хранящийся в памяти в базе. Другими словами, это не набор извлеченных данных из базы, это всего лишь указатель на них. Кроме того, курсор может последовательно перебирать данные через метод next(). Вызов этого метода приводит к тому, что содержимое объекта подменяется новой порцией данных от СУБД.

Извлечение данных из курсора требует преобразования типов, потому что типы данных в базе далеко не всегда совпадают с типами в Java. Поэтому при получении данных мы должны знать, в какой тип мы хотим преобразовать их.

Также отметим, что ResultSet тоже имеет метод close(), но он используется редко. Обычно ResultSet закрывается автоматически при закрытии стейтмента.


Самостоятельная работа

  1. Создайте новый Gradle-проект на своем компьютере
  2. Выполните все шаги из этого урока
  3. Создайте на GitHub репозиторий с именем hexlet-jdbc
  4. Залейте в него код нашего приложения

Дополнительные материалы

  1. Интерфейс PreparedStatement
  2. Интерфейс ResultSet

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff