Основы 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:. Проверьте, что драйвер подключён и адрес начинается с правильного префикса.