HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Преобразование сущностей в DTO и обратно - это довольно утомительная операция с большим объемом однообразного кода. Например, такого:</p>
1 <p>Преобразование сущностей в DTO и обратно - это довольно утомительная операция с большим объемом однообразного кода. Например, такого:</p>
2 <p>Кто-то в своих проектах выбирает такой подход, но есть и альтернатива. Существуют библиотеки, позволяющие автоматизировать конвертацию в обе стороны. Самая популярная из них - это<a>MapStruct</a>. В этом уроке мы подключим ее и научимся использовать.</p>
2 <p>Кто-то в своих проектах выбирает такой подход, но есть и альтернатива. Существуют библиотеки, позволяющие автоматизировать конвертацию в обе стороны. Самая популярная из них - это<a>MapStruct</a>. В этом уроке мы подключим ее и научимся использовать.</p>
3 <h2>Установка</h2>
3 <h2>Установка</h2>
4 <p>Для начала установим MapStruct:</p>
4 <p>Для начала установим MapStruct:</p>
5 <p>Кроме обычной зависимости, MapStruct требует еще и установки обработчика аннотации. Такие обработчики выполняются во время компиляции и используются для генерации кода. Ниже мы увидим, зачем это делается и как работает.</p>
5 <p>Кроме обычной зависимости, MapStruct требует еще и установки обработчика аннотации. Такие обработчики выполняются во время компиляции и используются для генерации кода. Ниже мы увидим, зачем это делается и как работает.</p>
6 <h2>Использование</h2>
6 <h2>Использование</h2>
7 <p>MapStruct работает следующим образом. С его помощью создаются специальные мапперы под каждую сущность. Внутри них определяются правила конвертирования в DTO или из DTO в зависимости от потребностей. Дальше эти мапперы используются в нужных местах, сводя преобразования к одной строчке.</p>
7 <p>MapStruct работает следующим образом. С его помощью создаются специальные мапперы под каждую сущность. Внутри них определяются правила конвертирования в DTO или из DTO в зависимости от потребностей. Дальше эти мапперы используются в нужных местах, сводя преобразования к одной строчке.</p>
8 <p>Разберем работу библиотеки на примере сущности Post, взятой из предыдущего урока. Чтобы было понятнее, мы начнем с конца. Сначала посмотрим, как использовать мапперы, а затем научимся их писать.</p>
8 <p>Разберем работу библиотеки на примере сущности Post, взятой из предыдущего урока. Чтобы было понятнее, мы начнем с конца. Сначала посмотрим, как использовать мапперы, а затем научимся их писать.</p>
9 <h3>Сущность</h3>
9 <h3>Сущность</h3>
10 <p>Определение сущности выглядит так:</p>
10 <p>Определение сущности выглядит так:</p>
11 <h3>DTO</h3>
11 <h3>DTO</h3>
12 <p>Для этой сущности реализуем три DTO для разных целей:</p>
12 <p>Для этой сущности реализуем три DTO для разных целей:</p>
13 <ul><li>Для создания сущности</li>
13 <ul><li>Для создания сущности</li>
14 <li>Для обновления сущности</li>
14 <li>Для обновления сущности</li>
15 <li>Для отображения сущности</li>
15 <li>Для отображения сущности</li>
16 </ul><p>Реализация выглядит так:</p>
16 </ul><p>Реализация выглядит так:</p>
17 <h3>Контроллер</h3>
17 <h3>Контроллер</h3>
18 <p>В конце мы напишем маппер, а пока посмотрим, как изменится код контроллера с их использованием. Все преобразование сведется к вызову postMapper.map():</p>
18 <p>В конце мы напишем маппер, а пока посмотрим, как изменится код контроллера с их использованием. Все преобразование сведется к вызову postMapper.map():</p>
19 <p>Код контроллера стал предельно простым. Вместо ручного копирования данных здесь используется маппер PostMapper, который содержит:</p>
19 <p>Код контроллера стал предельно простым. Вместо ручного копирования данных здесь используется маппер PostMapper, который содержит:</p>
20 <ul><li>Метод update()</li>
20 <ul><li>Метод update()</li>
21 <li>Перегруженный метод map(), работающий сразу с тремя классами:<ul><li>PostCreateDTO</li>
21 <li>Перегруженный метод map(), работающий сразу с тремя классами:<ul><li>PostCreateDTO</li>
22 <li>PostDTO</li>
22 <li>PostDTO</li>
23 <li>Post</li>
23 <li>Post</li>
24 </ul></li>
24 </ul></li>
25 </ul><h3>Мапперы</h3>
25 </ul><h3>Мапперы</h3>
26 <p>Перейдем к мапперам:</p>
26 <p>Перейдем к мапперам:</p>
27 <p>Маппер - это абстрактный класс с абстрактными методами для конвертации одних объектов в другие. Класс должен быть помечен аннотацией @Mapper с минимально указанной опцией componentModel = MappingConstants.ComponentModel.SPRING. Расположение класса, название класса и методов не фиксированы - программисты сами определяют, как все это организовать. MapStruct не ограничивает нас в DTO, мы можем преобразовывать объекты любых классов.</p>
27 <p>Маппер - это абстрактный класс с абстрактными методами для конвертации одних объектов в другие. Класс должен быть помечен аннотацией @Mapper с минимально указанной опцией componentModel = MappingConstants.ComponentModel.SPRING. Расположение класса, название класса и методов не фиксированы - программисты сами определяют, как все это организовать. MapStruct не ограничивает нас в DTO, мы можем преобразовывать объекты любых классов.</p>
28 <p>В документации MapStruct показаны примеры с интерфейсом, а не абстрактным классом. Технически эта библиотека работает и с интерфейсами, и абстрактными классами. Использовать последние удобнее, потому что в абстрактные классы можно сделать инъекцию зависимостей, если это необходимо.</p>
28 <p>В документации MapStruct показаны примеры с интерфейсом, а не абстрактным классом. Технически эта библиотека работает и с интерфейсами, и абстрактными классами. Использовать последние удобнее, потому что в абстрактные классы можно сделать инъекцию зависимостей, если это необходимо.</p>
29 <p>Во время компиляции происходит генерация конкретных мапперов. Посмотреть исходник этих классов можно в директории<em>build/generated/sources/annotationProcessor/java/main/io/spring/mapper</em>. Это очень упрощает отладку. Код маппера PostMapperImpl созданного на базе абстрактного класса PostMapper:</p>
29 <p>Во время компиляции происходит генерация конкретных мапперов. Посмотреть исходник этих классов можно в директории<em>build/generated/sources/annotationProcessor/java/main/io/spring/mapper</em>. Это очень упрощает отладку. Код маппера PostMapperImpl созданного на базе абстрактного класса PostMapper:</p>
30 <p>MapStruct самостоятельно написал тот код, который мы до этого писали руками. Но как он это сделал? MapStruct<a>сравнивает методы обоих классов</a>и автоматически распознает те, что совпадают. Кроме этого, MapStruct автоматически пытается преобразовать типы, если они не совпадают. В большинстве случаев это работает автоматически, но там где нет, всегда есть возможность дописать правила конвертации и преобразования типов. Для примера представим, что поле name переименовали в title. Если нам нужно сохранить внешнее API без изменений, то мы можем определить правила преобразования в маппере:</p>
30 <p>MapStruct самостоятельно написал тот код, который мы до этого писали руками. Но как он это сделал? MapStruct<a>сравнивает методы обоих классов</a>и автоматически распознает те, что совпадают. Кроме этого, MapStruct автоматически пытается преобразовать типы, если они не совпадают. В большинстве случаев это работает автоматически, но там где нет, всегда есть возможность дописать правила конвертации и преобразования типов. Для примера представим, что поле name переименовали в title. Если нам нужно сохранить внешнее API без изменений, то мы можем определить правила преобразования в маппере:</p>
31 <p>Аннотация @Mapping позволяет указать правила преобразования свойств. Самый частый случай - это когда имя свойства в исходном объекте не совпадает с целевым. В аннотации source указывает на объект, который передается как параметр, target - это объект, возвращаемый из метода.</p>
31 <p>Аннотация @Mapping позволяет указать правила преобразования свойств. Самый частый случай - это когда имя свойства в исходном объекте не совпадает с целевым. В аннотации source указывает на объект, который передается как параметр, target - это объект, возвращаемый из метода.</p>