Spring Data JDBC – генерация первичных ключей с помощью последовательностей (sequence)

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

По умолчанию Spring Data JDBC ожидает, что первичные ключи сущностей генерируются на стороне базы данных. В статье Introduction to Spring Data JDBC (Введение в Spring Data JDBC) мы использовали вариант с автоинкрементной колонкой, а в этой статье рассмотрим другой способ – использование последовательностей (sequence).

Spring Data JDBC, конечно, справится и с этим, но придется написать чуть больше кода: получить из базы данных очередное значение последовательности и установить первичный ключ перед сохранением сущности в базе данных. Это можно сделать, реализовав BeforeConvertCallback.

Реализация BeforeConvertCallback

Возможно, вы уже знакомы с механизмом колбэков (callback) в других модулях Spring Data. Entity Callback API появился в Spring Data Commons 2.2 и это официально рекомендуемый способ модификации объектов до или после определенных событий жизненного цикла. В Spring Data JDBC можно использовать этот механизм для получения значения последовательности при сохранении новой сущности.

Давайте используем этот подход для автоматического получения значения первичного ключа перед сохранением агрегата ChessGame.

public class ChessGame {
 
    @Id
    private Long id;
     
    private String playerWhite;
 
    private String playerBlack;
 
    private List<ChessMove> moves = new ArrayList<>();
 
    ...
}

Следующий пример, без каких-либо дополнительных настроек, сохраняет агрегат ChessGame и ожидает генерацию первичного ключа на стороне базы данных. Как уже упоминалось ранее, обычно для этого используется автоинкрементный столбец.

ChessGame game = new ChessGame();
game.setPlayerWhite("Thorben Janssen");
game.setPlayerBlack("A strong player");
 
ChessMove move1white = new ChessMove();
move1white.setMoveNumber(1);
move1white.setColor(MoveColor.WHITE);
move1white.setMove("e4");
game.getMoves().add(move1white);
 
ChessMove move1Black = new ChessMove();
move1Black.setMoveNumber(1);
move1Black.setColor(MoveColor.BLACK);
move1Black.setMove("e5");
game.getMoves().add(move1Black);
 
gameRepo.save(game);

Такой вариант генерации ключа нам не подходит, поэтому напишем свой BeforeConvertCallback. Этот колбэк выполнится перед сохранением ChessGame в базе данных.

Реализация колбэка довольно проста. Необходимо реализовать интерфейс BeforeConvertCallback, параметризуя его типом вашей сущности.

@Component
public class GetSequenceValueCallback implements BeforeConvertCallback<ChessGame> {
 
    private Logger log = LogManager.getLogger(GetSequenceValueCallback.class);
 
    private final JdbcTemplate jdbcTemplate;
 
    public GetSequenceValueCallback(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
 
    @Override
    public ChessGame onBeforeConvert(ChessGame game) {
        if (game.getId() == null) {
            log.info("Get the next value from a database sequence and use it as the primary key");
 
            Long id = jdbcTemplate.query("SELECT nextval('chessgame_seq')",
                    rs -> {
                        if (rs.next()) {
                            return rs.getLong(1);
                        } else {
                            throw new SQLException("Unable to retrieve value from sequence chessgame_seq.");
                        }
                    });
            game.setId(id);
        }
 
        return game;
    }
}

В реализации интерфейса должен быть определен конструктор, который принимает JdbcTemplate. Полученный JdbcTemplate мы можем использовать в реализации метода onBeforeConvert.

Spring Data JDBC выполняет BeforeConvertCallback как для операций INSERT, так и для UPDATE. Поэтому в методе onBeforeConvert следует проверить первичный ключ на null. В случае null присваиваем новое значение первичному ключу. Значение ключа получаем выполнив SQL-запрос через JdbcTemplate

Это все, что нужно сделать. При повторном запуске примера, приведенного выше, в логе вы увидите сообщения от GetSequenceValueCallback и SQL-оператор для получения значения последовательности.

16:00:22.891  INFO 6728 - – [           main] c.t.j.model.GetSequenceValueCallback     : Get the next value from a database sequence and use it as the primary key
16:00:22.892 DEBUG 6728 - – [           main] o.s.jdbc.core.JdbcTemplate               : Executing SQL query [SELECT nextval('chessgame_seq')]
16:00:22.946 DEBUG 6728 - – [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
16:00:22.947 DEBUG 6728 - – [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO "chess_game" ("id", "player_black", "player_white") VALUES (?, ?, ?)]
16:00:22.969 DEBUG 6728 - – [           main] o.s.jdbc.core.JdbcTemplate               : Executing SQL update and returning generated keys
16:00:22.970 DEBUG 6728 - – [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO "chess_move" ("chess_game", "chess_game_key", "color", "move", "move_number") VALUES (?, ?, ?, ?, ?)]
16:00:22.979 DEBUG 6728 - – [           main] o.s.jdbc.core.JdbcTemplate               : Executing SQL update and returning generated keys
16:00:22.980 DEBUG 6728 - – [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO "chess_move" ("chess_game", "chess_game_key", "color", "move", "move_number") VALUES (?, ?, ?, ?, ?)]

Выводы

Spring Data JDBC по умолчанию ожидает, что первичный ключ генерируется на стороне базы данных. Часто для этого используется столбец с автоинкрементом.

В этой статье мы рассмотрели, как создать собственный генератор значений первичного ключа, реализовав BeforeConvertCallback. Spring Data JDBC автоматически вызывает его при сохранении и обновлении агрегата, поэтому необходимо проверять необходимость генерации нового значения первичного ключа. Получить очередное значение последовательности из базы данных можно, выполнив SQL-запрос используя JdbcTemplate.


Приглашаем всех желающих на открытое занятие «Сборщик мусора в Java», на котором обсудим:
- Java Memory Model;
- 3 стадии и 2 поколения сборки мусора;
- Карьера и гибель объектов.
Регистрация на урок.

Источник: https://habr.com/ru/company/otus/blog/687386/


Интересные статьи

Интересные статьи

Недавно я поехал в Техас и купил трехрядный диатонический баян. Диатонические аккордеоны популярны для множества различных типов народной музыки, которую обычно учат на слух. Это хорошо для меня, пото...
Objection.js — сравнительно молодая и минималистичная ORM-библиотека для Node.js, которая сильно упрощает взаимодействие с базами данных и не перегружена дополнительными функциями, как Sequelize или T...
Приветствую всех!На протяжении долгого времени я не публиковал свежих выпусков Data Science Digest, а сейчас пришло время его возродить. Выходить дайджест будет еженедель...
Привет! Делимся переводом небольшой, но полезной статьи о том, как упростить процесс обновления данных для аутентификации. Установите редирект со страницы /.well-known/change-password...
У некоторых бизнес-тренеров в области е-коммерса и консультантов по увеличению интернет-продаж на многие вопросы часто можно слышать универсальную отмазку — «надо тестировать» или другую (чтобы не...