База данных через JDBC
Пока посты живут в памяти, после перезапуска сервера они пропадают. Пора сохранить их в базу данных. В Javalin нет своего слоя для баз данных: вы используете любую библиотеку Java. Мы возьмём обычный JDBC, знакомый по учебнику, и встраиваемую базу H2 — она работает внутри программы и хранит данные в файле, устанавливать её не нужно.
Зависимость
Добавьте драйвер H2 в pom.xml:
<!-- pom.xml -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.5.252</version>
</dependency>Подключение и таблица
DataSource — объект, который выдаёт соединения с базой. Класс Database создаёт его и таблицу постов:
// src/main/java/com/example/blog/Database.java
package com.example.blog;
import org.h2.jdbcx.JdbcDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Database {
public static DataSource create(String url) {
JdbcDataSource ds = new JdbcDataSource();
ds.setURL(url);
ds.setUser("sa");
ds.setPassword("");
createTables(ds);
return ds;
}
private static void createTables(DataSource ds) {
try (Connection conn = ds.getConnection();
Statement st = conn.createStatement()) {
st.execute("""
CREATE TABLE IF NOT EXISTS posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
body VARCHAR(5000) NOT NULL,
author VARCHAR(100) NOT NULL
)""");
} catch (SQLException e) {
throw new RuntimeException("Не удалось создать таблицы", e);
}
}
}Репозиторий
Репозиторий — класс, который прячет SQL за понятными методами. У него те же методы, что у PostStore, поэтому контроллер почти не изменится. Покажем два метода, остальные устроены так же:
// src/main/java/com/example/blog/PostRepository.java
public class PostRepository {
private final DataSource ds;
public PostRepository(DataSource ds) {
this.ds = ds;
}
public Optional<Post> findById(long id) {
String sql = "SELECT id, title, body, author FROM posts WHERE id = ?";
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, id);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? Optional.of(toPost(rs)) : Optional.empty();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Post create(NewPost data) {
String sql = "INSERT INTO posts (title, body, author) VALUES (?, ?, ?)";
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, data.title());
ps.setString(2, data.body());
ps.setString(3, data.author());
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
keys.next();
return new Post(keys.getLong(1), data.title(), data.body(), data.author());
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private Post toPost(ResultSet rs) throws SQLException {
return new Post(rs.getLong("id"), rs.getString("title"),
rs.getString("body"), rs.getString("author"));
}
// findAll() — SELECT ... ORDER BY id, delete(id) — DELETE FROM posts WHERE id = ?
}SQLException оборачивается в RuntimeException, и её ловит обработчик исключений из урока об ошибках.
Подключение к контроллеру
В PostController замените тип поля и параметра конструктора PostStore на PostRepository (имя поля store можно поменять на repo). В Main создайте репозиторий:
// src/main/java/com/example/blog/Main.java
var repo = new PostRepository(Database.create("jdbc:h2:./blog"));
var posts = new PostController(repo);Адрес jdbc:h2:./blog означает файл blog.mv.db в папке, откуда запущен сервер. Класс PostStore больше не нужен — удалите его.
Создайте пост, перезапустите сервер и запросите список:
curl -X POST http://localhost:8080/posts \
-d '{"title":"Привет, H2","body":"Посты теперь хранятся в базе.","author":"Олег"}'
curl http://localhost:8080/posts[{"id":1,"title":"Привет, H2","body":"Посты теперь хранятся в базе.","author":"Олег"}]
Пост пережил перезапуск. Добавьте *.db в .gitignore, чтобы файл базы не попал в Git.
Частая ошибка
База в памяти jdbc:h2:mem:blog удобна для тестов, но H2 удаляет её, когда закрывается последнее соединение. Таблица, созданная в Database.create, исчезает сразу, и любой следующий запрос к ней падает, например SELECT * FROM posts:
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "POSTS" not found (this database is empty); SQL statement:
SELECT * FROM posts [42104-252]
Для базы в памяти добавляйте параметр: jdbc:h2:mem:blog;DB_CLOSE_DELAY=-1 — тогда она живёт, пока работает программа.