Encodera

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

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

Оглавление

База данных через 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 — тогда она живёт, пока работает программа.

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

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