Spring Boot
2026-02-26 18:19 Diff

Преобразование сущностей в DTO и обратно — это довольно утомительная операция с большим объемом однообразного кода. Например, такого:

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

Установка

Для начала установим MapStruct:

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

Использование

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

Разберем работу библиотеки на примере сущности Post, взятой из предыдущего урока. Чтобы было понятнее, мы начнем с конца. Сначала посмотрим, как использовать мапперы, а затем научимся их писать.

Сущность

Определение сущности выглядит так:

DTO

Для этой сущности реализуем три DTO для разных целей:

  • Для создания сущности
  • Для обновления сущности
  • Для отображения сущности

Реализация выглядит так:

Контроллер

В конце мы напишем маппер, а пока посмотрим, как изменится код контроллера с их использованием. Все преобразование сведется к вызову postMapper.map():

Код контроллера стал предельно простым. Вместо ручного копирования данных здесь используется маппер PostMapper, который содержит:

  • Метод update()
  • Перегруженный метод map(), работающий сразу с тремя классами:
    • PostCreateDTO
    • PostDTO
    • Post

Мапперы

Перейдем к мапперам:

Маппер — это абстрактный класс с абстрактными методами для конвертации одних объектов в другие. Класс должен быть помечен аннотацией @Mapper с минимально указанной опцией componentModel = MappingConstants.ComponentModel.SPRING. Расположение класса, название класса и методов не фиксированы — программисты сами определяют, как все это организовать. MapStruct не ограничивает нас в DTO, мы можем преобразовывать объекты любых классов.

В документации MapStruct показаны примеры с интерфейсом, а не абстрактным классом. Технически эта библиотека работает и с интерфейсами, и абстрактными классами. Использовать последние удобнее, потому что в абстрактные классы можно сделать инъекцию зависимостей, если это необходимо.

Во время компиляции происходит генерация конкретных мапперов. Посмотреть исходник этих классов можно в директории build/generated/sources/annotationProcessor/java/main/io/spring/mapper. Это очень упрощает отладку. Код маппера PostMapperImpl созданного на базе абстрактного класса PostMapper:

MapStruct самостоятельно написал тот код, который мы до этого писали руками. Но как он это сделал? MapStruct сравнивает методы обоих классов и автоматически распознает те, что совпадают. Кроме этого, MapStruct автоматически пытается преобразовать типы, если они не совпадают. В большинстве случаев это работает автоматически, но там где нет, всегда есть возможность дописать правила конвертации и преобразования типов. Для примера представим, что поле name переименовали в title. Если нам нужно сохранить внешнее API без изменений, то мы можем определить правила преобразования в маппере:

Аннотация @Mapping позволяет указать правила преобразования свойств. Самый частый случай — это когда имя свойства в исходном объекте не совпадает с целевым. В аннотации source указывает на объект, который передается как параметр, target — это объект, возвращаемый из метода.