Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Основы JDBC

Данные, которые должны пережить перезапуск программы, — пользователей, заказы, товары — обычно хранят в реляционной базе данных: PostgreSQL, MySQL, SQLite. Данные в ней лежат в таблицах, а работают с ними на языке SQL. Java общается с любой такой базой через JDBC (Java Database Connectivity) — стандартный набор интерфейсов из пакета java.sql. Код один и тот же, меняется только драйвер и адрес базы.

Драйвер

Драйвер — библиотека, которая реализует интерфейсы JDBC для конкретной базы. В уроках главы используем SQLite: она хранит базу в одном файле или прямо в памяти и не требует установки сервера. В проекте на Maven драйвер подключают зависимостью:

<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.53.4.0</version>
</dependency>

В Gradle — строкой implementation("org.xerial:sqlite-jdbc:3.53.4.0"). Чтобы запустить одиночный файл, скачайте sqlite-jdbc-3.53.4.0.jar с сайта Maven Central, положите рядом с Main.java под именем sqlite-jdbc.jar и передайте его через -cp:

java --enable-native-access=ALL-UNNAMED -cp sqlite-jdbc.jar Main.java

Драйвер SQLite загружает машинный код, и Java 25 без ключа --enable-native-access печатает об этом предупреждение. Так запускаются все примеры главы.

Подключение

Соединение с базой — объект Connection. Его открывает DriverManager.getConnection(url): по адресу (URL) он сам находит подходящий драйвер. Адрес jdbc:sqlite::memory: создаёт временную базу в памяти. Соединение — ресурс, поэтому его закрывают через try-with-resources. Пакет java.sql не входит в java.base, так что даже в компактном файле нужен import:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")) {
        DatabaseMetaData meta = conn.getMetaData();
        IO.println("База: " + meta.getDatabaseProductName() + " " + meta.getDatabaseProductVersion());
        IO.println("Драйвер: " + meta.getDriverName());
    }
}

Результат:

База: SQLite 3.53.4
Драйвер: SQLite JDBC

Почти все методы JDBC могут выбросить проверяемое исключение SQLException, поэтому у main стоит throws SQLException.

Statement: команды SQL

Statement выполняет команды SQL. Для команд, которые меняют данные или структуру — CREATE TABLE, INSERT, UPDATE, DELETE, — вызывают executeUpdate. Он возвращает число затронутых строк:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
         Statement st = conn.createStatement()) {
        st.executeUpdate("CREATE TABLE products (id INTEGER PRIMARY KEY, title TEXT, price REAL)");
 
        int added = st.executeUpdate("""
                INSERT INTO products (title, price)
                VALUES ('Чай', 4.5), ('Кофе', 7.25), ('Какао', 5.0)""");
        IO.println("Добавлено строк: " + added);
    }
}

Результат:

Добавлено строк: 3

Столбец id INTEGER PRIMARY KEY база заполняет сама: 1, 2, 3. Длинные запросы удобно писать в текстовых блоках """.

ResultSet: чтение строк

Запрос SELECT выполняют методом executeQuery. Он возвращает ResultSet — курсор по строкам результата. next() переходит к следующей строке и возвращает false, когда строки кончились. Значения столбцов берут методами getString, getInt, getDouble по имени столбца:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
         Statement st = conn.createStatement()) {
        st.executeUpdate("CREATE TABLE products (id INTEGER PRIMARY KEY, title TEXT, price REAL)");
        st.executeUpdate("INSERT INTO products (title, price) VALUES ('Чай', 4.5), ('Кофе', 7.25), ('Какао', 5.0)");
 
        try (ResultSet rs = st.executeQuery("SELECT id, title, price FROM products ORDER BY price")) {
            while (rs.next()) {
                int id = rs.getInt("id");
                String title = rs.getString("title");
                double price = rs.getDouble("price");
                IO.println(id + ". " + title + " — $" + price);
            }
        }
    }
}

Результат:

1. Чай — $4.5
3. Какао — $5.0
2. Кофе — $7.25

Сразу после executeQuery курсор стоит перед первой строкой, поэтому next() вызывают до чтения. Столбцы можно брать и по номеру — rs.getString(2), — нумерация начинается с 1.

Другие базы

С PostgreSQL или MySQL код не меняется — только драйвер в зависимостях (org.postgresql:postgresql, com.mysql:mysql-connector-j) и адрес с именем пользователя и паролем:

// src/main/java/shop/Database.java
Connection conn = DriverManager.getConnection(
        "jdbc:postgresql://localhost:5432/shop", "shop_user", System.getenv("DB_PASSWORD"));

Для MySQL адрес выглядит так: jdbc:mysql://localhost:3306/shop. А SQLite-база в файле — jdbc:sqlite:shop.db. Пароль не пишут в коде, а берут, например, из переменной окружения.

Частая ошибка

Если драйвера нет в classpath — забыли -cp sqlite-jdbc.jar или зависимость в сборке, — DriverManager не найдёт, кому передать адрес:

import java.sql.*;
 
void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:")) {
        IO.println("Подключились");
    }
}

Запуск без драйвера:

java Main.java

Результат:

Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:sqlite::memory:
	at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:638)
	at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:222)
	at Main.main(Main.java:4)

Та же ошибка бывает при опечатке в начале адреса, например jdbc:sqllite:. Проверьте, что драйвер подключён и адрес начинается с правильного префикса.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.