Как выполняется маппинг объектов в DTO и какие инструменты используются
Что проверяют
Понимаете ли вы, зачем нужен отдельный DTO и чем инструменты маппинга отличаются по сути, а не по популярности. Следом обычно спрашивают, как маппятся вложенные объекты и коллекции, и где в приложении этот маппинг должен жить.
Как отвечать
JPA-сущность наружу не отдают. У неё ленивые связи, циклические ссылки для JSON и поля, которые клиенту видеть нельзя. DTO фиксирует контракт API отдельно от схемы базы, и с Java 16 его удобно объявлять как record.
Дальше способы:
- вручную: конструктор, фабричный метод
UserDto.from(user)или отдельный класс-маппер. Прозрачно, компилятор ловит ошибки, но на десятках полей это рутина; - MapStruct: annotation processor, который на этапе компиляции генерирует обычный Java-код. Интерфейс с
@Mapper(componentModel = "spring")становится Spring-бином, разные имена полей связывают через@Mapping(target = "fullName", source = "name"), обновление существующего объекта делают через@MappingTarget. НастройкаunmappedTargetPolicy = ReportingPolicy.ERRORпревращает забытое поле в ошибку сборки; - ModelMapper: рефлексия в рантайме и сопоставление по именам с эвристиками. Подключается за минуту, но ошибки видны только при выполнении, а «умное» сопоставление может молча положить значение не в то поле;
- проекции Spring Data: интерфейсные проекции или
select new ...UserDto(...)в JPQL. Сущность вообще не загружается целиком, база отдаёт только нужные колонки.
Маппинг обычно держат в сервисном слое, пока открыта транзакция и ленивые связи ещё доступны.
Где спотыкаются
Маппят ленивые коллекции вне транзакции и получают LazyInitializationException. Или маппят внутри, но на каждую связь уходит отдельный запрос, то есть N+1. Лечится JOIN FETCH, @EntityGraph или проекцией.
Сравнивают MapStruct и ModelMapper только словом «быстрее». Главное отличие другое: генерация кода при компиляции против рефлексии в рантайме, а значит ошибка при сборке против ошибки у пользователя.
Маппят двусторонние связи в лоб: заказ ссылается на пользователя, пользователь на список заказов, и дело кончается StackOverflowError. Обратную сторону связи в DTO заменяют идентификатором.