Еще один способ использования Java records как DTO

от автора

В данной статье будет рассмотрен способ применения Java records в качестве DTO (data transfer objects).

Используем Spring Boot / Hibernate.

Представленный далее код не предназначен для продакшена. Это, скорее, размышления на тему. Возможно кому-то будет интересно и полезно.

Цель — за пределами сервисного слоя использовать только DTO и не таскать сущности с persistence context’ом по бизнес-логике.

Обычно использование паттерна DTO подразумевает применение отдельных функций для преобразования модели в DTO и обратно.
По сути имеем три задачи — сохранение/выборку данных и удобный способ работы с ними.

В демо проекте реализованы две сущности — Note и Tag, со связями ManyToMany.

Далее описаны способы выборки и обновления данных на примере Tag.

Для выборки данных в repository используем JPQL Constructor Expressions:

    @Transactional(readOnly = true)     @Query(value = "SELECT new dev.isdn.demo.records_dto.app.domain.tag.TagDto(t.id, t.name, t.color) FROM tags t WHERE t.id = :id")     Optional<TagDto> findById(@Param("id") long id);

Получаем результат сразу в record, завернутый в Optional. Если выборка пустая — то получаем пустой Optional.

Соответственно, в сервисном слое просто передаем результат:

    public Optional<TagDto> getTagById(long tagId) {         return repository.findById(tagId);     }

Для сохранения в базу нужно будет сделать несколько дополнительных действий:

Optional.ofNullable(tagDto) .flatMap(t -> repository.getTagById(t.id())) // проверяем наличие записи с таким ID в базе и преобразуем DTO в entity .flatMap(t -> setTagNameAndColor(t, content.name(), content.color())) // обновляем entity

В данном случае очень удобно использовать фичи Optional для проверки результата на каждом шаге.

Метод setTagNameAndColor() выполняет проверку входных данных и обновляет сущность.

В итоге сделал вот такой сервисный метод в декларативном стиле:

    @Transactional     public Optional<TagDto> updateTagContent(TagDto tag, TagContent content) {         return Functions.checkTagDto.apply(tag)                 .flatMap(t -> repository.getTagById(t.id()))                 .flatMap(t -> setTagNameAndColor(t, content.name(), content.color()))                 .map(repository::saveAndFlush)                 .flatMap(t -> repository.findById(t.getId()));     }

Такой подход выглядит немного избыточным, сделано исключительно в демонстрационных целях.

Функция checkTagDto делает простую проверку Optional.ofNullable(tag).filter(t -> t.id() > 0).

Более сложные выборки из базы в repository можно делать аналогично.
Например, получение связанных данных:

    @QueryHints(value = {             @QueryHint(name = HINT_FETCH_SIZE, value = "100"),             @QueryHint(name = READ_ONLY, value = "true")     })     @Transactional(readOnly = true)     @Query(value = "SELECT new dev.isdn.demo.records_dto.app.domain.tag.TagDto(t.id, t.name, t.color) FROM tags t INNER JOIN t.notes n WHERE n.id = :noteId")     Stream<TagDto> findAllByNoteId(@Param("noteId") long noteId);

    @QueryHints(value = {             @QueryHint(name = HINT_FETCH_SIZE, value = "100"),             @QueryHint(name = READ_ONLY, value = "true")     })     @Transactional(readOnly = true)     @Query(value = "SELECT new dev.isdn.demo.records_dto.app.domain.note.NoteDto(n.id, n.created, n.modified, n.content) FROM notes n INNER JOIN n.tags t WHERE t.id = :tagId")     Stream<NoteDto> findAllByTagId(@Param("tagId") long tagId);

Теперь о том, как с этим работать.

Простой пример REST контроллера:

    @PutMapping(PREFIX + VERSION + "/tags/{id}")     TagDto updateTagContent(@PathVariable long id, @RequestBody TagContent content) {         TagDto tag = tagService.getTagById(id).orElseThrow(() -> new NoSuchItemException("tag " + id));         return tagService.updateTagContent(tag, content).orElseThrow(() -> new NotUpdatedException("tag " + id));     }      @GetMapping(PREFIX + VERSION + "/tags/{id}/notes")     List<NoteDto> getTagNotes(@PathVariable long id) {         TagDto tag = tagService.getTagById(id).orElseThrow(() -> new NoSuchItemException("tag " + id));         return noteService.getTagNotes(tag);     }

Полностью код можно посмотреть вот здесь.


ссылка на оригинал статьи https://habr.com/ru/post/693648/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *