DAO (data access object) - объект для доступа к данным
DTO (data transfer object) - объект для передачи даннных
На прошлом занятии мы убедились, что использование Entity (они же DAO) для передачи данных не подходит из-за возможного возникновения циклических связей, которые формат передачи данных JSON не поддерживает.
Для того, чтобы обойти эту проблему, необходимо завести DTO-классы, которые будут разрывать возникающий цикл. В проекте в пакете ru.lstu.demo2.dto
созданы новые классы.
Первым создан класс AuthorDto
, который содержит необходимую информацию для идентификации автора, но не содержит списка произведений.
Вторым создан класс AuthorWithBooksDto
, содержащий список книг, написанных автором. Обратите внимание, что для уменьшения написанного кода используется уже написанный AuthorDto
(прием наследование). AuthorWithBooksDto
, при этом содержит в себе и информацию об авторе (унаследованную), и информацию по книгам.
Третий класс BookDto
содержит информаицю о произведении, и об авторе (ссылка на AuthorDto
для исключения зацикливания.)
Следующим шагом идет обеспечение преобразования от DAO к DTO и наоборот. Преобразование объектов от одного к другому может потребовать написания дополнительной логики и кода, но мы постараемся избежать рутинных операций. Есть достаточно мощный инстрементарий MapStruct, который поможет нам в этом вопросе.
Для подключения MapStruct добавим две зависимости в файл build.gradle:
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
Теперь можно перейти к настройке преобразования объектов, быстрой настройке MapStruct
В пакете ru.lstu.demo2.mapper
создаем интерфейсы и в них объявляем методы для преобразования Entity в Dto и наоборот. Также создаем методы для преобразования списков объектов (List), эту конструкцию mapstruct тоже поддерживает.
Чтобы заработала генерация кода нужно просто аннотировать интерфейсы аннотацией @Mapper
. Дополнительно укажем в свойствах аннотации componentModel="spring"
, что является инструкцией создать корректные с точки зрения Spring компоненты.
Также, если один интерфейс использует другой (например, для создания BookDto нам нужен AuthorDto), используется инструкция uses
. Эта конструкция позволяет использовать ранее сгенерированный код, вместо повторной генерации его по своим правилам.
Код, генерируемый mapstruct, просто присваивает значение свойств объекта другому свойству с таким же названием (id->id, name->name), хотя есть возможность более глубокой настройки генерации кода. Например, в классе AuthorMapper
есть инструкция, описанная в аннотации @Mapping
, что поле books нужно игнорировать и не заполнять. Впрочем, и без этой инструкции код сгенерировался бы рабочим, просто при компиляции выводилось предупреждение, что mapstruct не нашел, откуда заполнить это значение.
Все, что осталось сделать для успешной работы проекта - добавить в контроллеры AuthorController
и BookController
ссылки на мэпперы ивызывать соответствующие функции для того, чтобы возвращать Dto вместо entity-объектов.