MapStruct для Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое MapStruct и зачем он нужен?

MapStruct — это генератор кода для Java, который автоматически создает реализации мапперов (преобразователей) между объектами разных типов. В современной Java-разработке часто возникает необходимость преобразовывать один объект в другой: Entity в DTO, DTO в VO, один API-ответ в другую модель и так далее. Ручное написание такого кода — это скучная, трудоемкая и чреватая ошибками работа. MapStruct решает эту проблему, генерируя весь boilerplate-код на этапе компиляции.

Основные преимущества MapStruct:

  • Производительность: Генерация кода на этапе компиляции (compile-time). Нет рефлексии, нет потерь производительности в runtime. MapStruct работает так же быстро, как если бы вы написали маппинг вручную.
  • Безопасность типов: Все ошибки (несоответствие типов, отсутствие полей) обнаруживаются на этапе компиляции, а не в runtime.
  • Простота: Минимум конфигурации. Достаточно определить интерфейс или абстрактный класс с методами маппинга, и MapStruct сам сгенерирует реализацию.
  • Гибкость: Поддержка сложных маппингов, преобразования типов, использования выражений, вызова методов и интеграции с другими библиотеками (Lombok, Spring и т.д.).
  • Чистота кода: Код маппинга отделен от бизнес-логики, что делает его более читаемым и поддерживаемым.

MapStruct особенно полезен в многослойных архитектурах (Controller-Service-Repository), где часто требуется преобразовывать объекты между слоями.

Установка MapStruct

MapStruct не устанавливается через pip или npm, так как это библиотека для Java. Для подключения MapStruct к вашему проекту используется система сборки Maven или Gradle.

Установка через Maven

Добавьте следующие зависимости в ваш файл pom.xml:

<dependencies>
    <!-- Основная библиотека MapStruct -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>

    <!-- Процессор аннотаций (для генерации кода) -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.5.5.Final</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Установка через Gradle

Для Gradle добавьте в файл build.gradle:

dependencies {
    implementation 'org.mapstruct:mapstruct:1.5.5.Final'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
}

Важно: Убедитесь, что версия mapstruct-processor совпадает с версией mapstruct. После добавления зависимостей выполните сборку проекта (например, mvn clean compile), чтобы MapStruct сгенерировал реализации мапперов.

Быстрый старт с примером кода

Давайте создадим простой маппер, который преобразует объект Car (Entity) в объект CarDto (DTO).

Шаг 1: Определите исходный и целевой классы

// Car.java
public class Car {
    private String make;
    private int numberOfSeats;
    private CarType type;

    // конструкторы, геттеры, сеттеры
    public Car() {}

    public Car(String make, int numberOfSeats, CarType type) {
        this.make = make;
        this.numberOfSeats = numberOfSeats;
        this.type = type;
    }

    public String getMake() { return make; }
    public void setMake(String make) { this.make = make; }
    public int getNumberOfSeats() { return numberOfSeats; }
    public void setNumberOfSeats(int numberOfSeats) { this.numberOfSeats = numberOfSeats; }
    public CarType getType() { return type; }
    public void setType(CarType type) { this.type = type; }
}

// CarType.java
public enum CarType {
    SEDAN, SUV, SPORTS, HATCHBACK
}

// CarDto.java
public class CarDto {
    private String make;
    private int seatCount;
    private String type;

    // конструкторы, геттеры, сеттеры
    public CarDto() {}

    public CarDto(String make, int seatCount, String type) {
        this.make = make;
        this.seatCount = seatCount;
        this.type = type;
    }

    public String getMake() { return make; }
    public void setMake(String make) { this.make = make; }
    public int getSeatCount() { return seatCount; }
    public void setSeatCount(int seatCount) { this.seatCount = seatCount; }
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
}

Шаг 2: Создайте интерфейс маппера

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(source = "numberOfSeats", target = "seatCount")
    @Mapping(source = "type", target = "type")
    CarDto carToCarDto(Car car);
}

Шаг 3: Используйте маппер

public class Main {
    public static void main(String[] args) {
        Car car = new Car("Toyota", 5, CarType.SEDAN);

        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);

        System.out.println("Make: " + carDto.getMake());
        System.out.println("Seat Count: " + carDto.getSeatCount());
        System.out.println("Type: " + carDto.getType());
    }
}

Результат:

Make: Toyota
Seat Count: 5
Type: SEDAN

Обратите внимание, что MapStruct автоматически сопоставил поле make (имена совпадают), преобразовал numberOfSeats в seatCount (благодаря аннотации @Mapping) и преобразовал enum CarType в строку type.

Основные методы и аннотации MapStruct

MapStruct предоставляет богатый набор аннотаций и конфигураций для настройки маппинга. Рассмотрим 10 ключевых из них.

1. @Mapper

Что делает: Помечает интерфейс или абстрактный класс как маппер. MapStruct сгенерирует реализацию для всех методов, объявленных в этом интерфейсе.

Параметры:

  • componentModel — модель компонента (например, "spring" для интеграции со Spring).
  • uses — классы других мапперов, которые могут быть использованы.
  • unmappedTargetPolicy — политика обработки несопоставленных полей (ERROR, WARN, IGNORE).

Пример:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface UserMapper {
    UserDto userToUserDto(User user);
}

2. @Mapping

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

Параметры:

  • source — имя поля в исходном объекте.
  • target — имя поля в целевом объекте.
  • expression — Java-выражение для преобразования.
  • defaultValue — значение по умолчанию, если поле источника равно null.
  • qualifiedByName — имя квалификатора для вызова кастомного метода.

Пример:

@Mapping(source = "fullName", target = "name")
@Mapping(source = "emailAddress", target = "email")
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
UserDto userToUserDto(User user);

3. @Mappings

Что делает: Контейнер для нескольких аннотаций @Mapping. Используется, когда нужно применить более одного маппинга к одному методу.

Параметры: Не имеет собственных параметров, содержит массив @Mapping.

Пример:

@Mappings({
    @Mapping(source = "firstName", target = "givenName"),
    @Mapping(source = "lastName", target = "familyName"),
    @Mapping(source = "age", target = "yearsOld")
})
PersonDto personToPersonDto(Person person);

4. @MappingTarget

Что делает: Используется для обновления существующего объекта, а не создания нового. Параметр метода, помеченный этой аннотацией, будет тем объектом, в который будут записаны значения.

Параметры: Не имеет.

Пример:

@Mapper
public interface CarMapper {
    void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}

// Использование:
Car car = new Car("Toyota", 5, CarType.SEDAN);
CarDto carDto = new CarDto("Honda", 4, "SUV");
carMapper.updateCarFromDto(carDto, car);
// Теперь car имеет значения из carDto: make="Honda", seatCount=4, type=SUV

5. @IterableMapping

Что делает: Позволяет настроить маппинг для коллекций (List, Set и т.д.). Полезно, когда нужно преобразовать каждый элемент коллекции.

Параметры:

  • elementTargetType — тип элемента целевой коллекции.
  • dateFormat — формат даты для преобразования.

Пример:

@Mapper
public interface CarMapper {
    @IterableMapping(elementTargetType = CarDto.class)
    List<CarDto> carsToCarDtos(List<Car> cars);
}

// MapStruct сгенерирует код, который пройдется по списку и преобразует каждый Car в CarDto.

6. @BeanMapping

Что делает: Позволяет настроить общие правила маппинга для всего метода. Например, можно игнорировать все поля, которые не были явно сопоставлены.

Параметры:

  • ignoreByDefault — если true, то будут сопоставлены только поля, явно указанные в @Mapping.
  • nullValueMappingStrategy — стратегия обработки null значений.

Пример:

@BeanMapping(ignoreByDefault = true)
@Mapping(source = "id", target = "userId")
@Mapping(source = "name", target = "userName")
UserDto userToUserDto(User user);
// Будут сопоставлены только id и name, остальные поля в UserDto останутся null.

7. @AfterMapping / @BeforeMapping

Что делает: Позволяют выполнить кастомную логику до или после основного маппинга. Методы с этими аннотациями должны быть определены в том же маппере (как default-методы).

Параметры: Не имеют.

Пример:

@Mapper
public interface OrderMapper {

    @Mapping(source = "totalPrice", target = "price")
    OrderDto orderToOrderDto(Order order);

    @AfterMapping
    default void setFormattedDate(Order order, @MappingTarget OrderDto order                    

Рекомендаций