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

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

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

Начать курс

ModelMapper для Java: Полное руководство по автоматическому маппингу объектов

1. Что такое ModelMapper и зачем он нужен?

ModelMapper — это мощная библиотека для Java, предназначенная для автоматического преобразования (маппинга) объектов одного типа в объекты другого типа. В современной разработке, особенно при использовании многослойной архитектуры (Controller → Service → Repository), разработчики постоянно сталкиваются с необходимостью копировать данные между DTO (Data Transfer Objects), Entity (сущностями базы данных), VO (Value Objects) и другими POJO. Ручное написание такого кода — это утомительная, подверженная ошибкам работа, которая приводит к раздуванию кода и снижению его читаемости.

Основная цель ModelMapper — избавить разработчика от написания шаблонного кода вида source.getField() → target.setField(). Библиотека анализирует структуру классов (поля, геттеры/сеттеры) и автоматически сопоставляет их, используя соглашения об именовании (convention over configuration). Она поддерживает вложенные объекты, коллекции, наследование и сложные трансформации, что делает её незаменимым инструментом в корпоративных приложениях на Java.

ModelMapper особенно полезен в следующих сценариях:

  • Слои приложения: Преобразование Entity в DTO и обратно для передачи данных через REST API.
  • Микросервисы: Маппинг между объектами разных сервисов.
  • Рефакторинг: Когда структура классов меняется, достаточно изменить маппинг в одном месте.
  • Тестирование: Быстрое создание тестовых данных на основе существующих объектов.

2. Установка

ModelMapper распространяется через Maven Central. Для его установки необходимо добавить зависимость в файл pom.xml (для Maven) или build.gradle (для Gradle).

Maven (pom.xml):

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.2.0</version>
</dependency>

Gradle (build.gradle):

implementation 'org.modelmapper:modelmapper:3.2.0'

Примечание: Версия 3.2.0 актуальна на момент написания статьи. Рекомендуется проверять последнюю версию на MVNRepository.

3. Быстрый старт — минимальный рабочий пример

Рассмотрим простейший пример: у нас есть класс UserEntity (сущность базы данных) и UserDTO (объект для передачи данных).

// Исходный класс (Entity)
public class UserEntity {
    private Long id;
    private String firstName;
    private String lastName;
    private String email;

    // Конструкторы, геттеры и сеттеры
    public UserEntity() {}
    public UserEntity(Long id, String firstName, String lastName, String email) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

// Целевой класс (DTO)
public class UserDTO {
    private Long id;
    private String fullName; // Обратите внимание: поле называется иначе
    private String emailAddress;

    // Конструкторы, геттеры и сеттеры
    public UserDTO() {}
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    public String getEmailAddress() { return emailAddress; }
    public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; }
}

// Основной класс с примером
import org.modelmapper.ModelMapper;

public class Main {
    public static void main(String[] args) {
        // 1. Создаем экземпляр ModelMapper
        ModelMapper modelMapper = new ModelMapper();

        // 2. Создаем исходный объект
        UserEntity user = new UserEntity(1L, "John", "Doe", "john.doe@example.com");

        // 3. Выполняем маппинг
        UserDTO userDTO = modelMapper.map(user, UserDTO.class);

        // 4. Выводим результат
        System.out.println("ID: " + userDTO.getId());
        System.out.println("Full Name: " + userDTO.getFullName()); // null! (см. раздел 4)
        System.out.println("Email: " + userDTO.getEmailAddress()); // null!
    }
}

Почему fullName и emailAddress равны null? Потому что ModelMapper по умолчанию сопоставляет поля по имени (firstName → firstName, lastName → lastName). Поле fullName в DTO не совпадает ни с одним полем в Entity. Для таких случаев нужно использовать явные маппинги (см. раздел 4).

4. Основные методы/функции/классы

4.1. ModelMapper.map(Object source, Class<D> destinationType)

Сигнатура: public <D> D map(Object source, Class<D> destinationType)

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

Пример:

UserEntity source = new UserEntity(1L, "Alice", "Smith", "alice@test.com");
UserDTO result = modelMapper.map(source, UserDTO.class);
// result.getId() == 1L

4.2. ModelMapper.map(Object source, Object destination)

Сигнатура: public void map(Object source, Object destination)

Что делает: Маппинг в уже существующий объект (destination). Полезно, когда нужно обновить поля существующего объекта, а не создавать новый.

Пример:

UserEntity source = new UserEntity(2L, "Bob", "Brown", "bob@test.com");
UserDTO existingDTO = new UserDTO();
existingDTO.setId(99L); // Старое значение

modelMapper.map(source, existingDTO);
// existingDTO.getId() == 2L (перезаписано из source)

4.3. PropertyMap — настройка маппинга полей

Сигнатура: public abstract class PropertyMap<S, D> { protected abstract void configure(); }

Что делает: Позволяет вручную определить, как поля исходного объекта (S) преобразуются в поля целевого объекта (D). Используется для решения конфликтов имен или для применения трансформаций.

Пример: Преобразуем firstName + " " + lastName в fullName.

import org.modelmapper.PropertyMap;

PropertyMap<UserEntity, UserDTO> userMap = new PropertyMap<>() {
    @Override
    protected void configure() {
        map().setFullName(source.getFirstName() + " " + source.getLastName());
        map().setEmailAddress(source.getEmail());
    }
};

modelMapper.addMappings(userMap);
UserDTO result = modelMapper.map(user, UserDTO.class);
// result.getFullName() == "John Doe"

4.4. TypeMap — конфигурация для пары типов

Сигнатура: public <S, D> TypeMap<S, D> createTypeMap(Class<S> sourceType, Class<D> destinationType)

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

Пример:

TypeMap<UserEntity, UserDTO> typeMap = modelMapper.createTypeMap(UserEntity.class, UserDTO.class);
typeMap.addMappings(mapper -> {
    mapper.map(UserEntity::getEmail, UserDTO::setEmailAddress);
    mapper.using(ctx -> ((String)ctx.getSource()).toUpperCase())
          .map(UserEntity::getFirstName, UserDTO::setFullName);
});

UserDTO result = modelMapper.map(user, UserDTO.class);
// result.getEmailAddress() == "john.doe@example.com"

4.5. Converter — пользовательский конвертер

Сигнатура: public interface Converter<S, D> { D convert(MappingContext<S, D> context); }

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

Пример: Преобразование String (дата) в LocalDate.

import org.modelmapper.Converter;
import org.modelmapper.spi.MappingContext;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

Converter<String, LocalDate> stringToDate = new Converter<>() {
    @Override
    public LocalDate convert(MappingContext<String, LocalDate> context) {
        return LocalDate.parse(context.getSource(), DateTimeFormatter.ISO_LOCAL_DATE);
    }
};

modelMapper.addConverter(stringToDate);
// Теперь все поля String → LocalDate будут использовать этот конвертер

4.6. Provider — кастомное создание целевых объектов

Сигнатура: public interface Provider<T> { T get(); }

Что делает: Определяет, как создавать экземпляры целевого класса, если конструктор по умолчанию не подходит (например, если класс использует билдер или фабрику).

Пример:

import org.modelmapper.Provider;

Provider<UserDTO> userDTOProvider = new Provider<>() {
    @Override
    public UserDTO get() {
        return UserDTO.builder().build(); // Предположим, есть билдер
    }
};

modelMapper.createTypeMap(UserEntity.class, UserDTO.class)
           .setProvider(userDTOProvider);

4.7. Condition — условный маппинг

Сигнатура: public interface Condition<S, D> { boolean applies(MappingContext<S, D> context); }

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

Пример: Копируем email только если он не пустой.

import org.modelmapper.Condition;
import org.modelmapper.spi.MappingContext;

Condition<String, String> notEmpty = new Condition<>() {
    @Override
    public boolean applies(MappingContext<String, String> context) {
        return context.getSource() != null && !context.getSource().isEmpty();
    }
};

modelMapper.createTypeMap(UserEntity.class, UserDTO.class)
           .addMappings(mapper -> mapper.when(notEmpty).map(UserEntity::getEmail, UserDTO::setEmailAddress));

4.8. ModelMapper.getConfiguration() — глобальная настройка

Сигнатура: public Configuration getConfiguration()

Что делает: Возвращает объект конфигурации, который позволяет настроить глобальное поведение маппера: режим совпадения полей (MATCHING_STRATEGY), обработка null, игнорирование неизвестных полей и т.д.

Пример:

import org.modelmapper.config.Configuration.AccessLevel;
import org.modelmapper.convention.MatchingStrategies;

modelMapper.getConfiguration()
    .setMatchingStrategy(MatchingStrategies.STRICT) // Строгое совпадение имен
    .setFieldMatchingEnabled(true)                  // Маппинг по полям (а не только по геттерам/сеттерам)
    .setFieldAccessLevel(AccessLevel.PRIVATE)       // Доступ к приватным полям
    .setSkipNullEnabled(true);                      // Пропускать null значения при маппинге

5. Реальный практический пример (150+ строк кода)

Рассмотрим полноценный пример: маппинг между сущностью Order и DTO OrderResponse с вложенными объектами и вычисляемыми полями.

// ========== Модели данных ==========

// Исходная сущность (Entity)
class OrderEntity {
    private Long id;
    private String orderNumber;
    private CustomerEntity customer;
    private List<OrderItemEntity> items;
    private LocalDateTime createdAt;

    // Геттеры и сеттеры...
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getOrderNumber() { return orderNumber; }
    public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
    public CustomerEntity getCustomer() { return customer; }
    public void setCustomer(CustomerEntity customer) { this.customer = customer; }
    public List<OrderItemEntity> getItems() { return items; }
    public void setItems(List<OrderItemEntity> items) { this.items = items; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

class CustomerEntity {
    private Long id;
    private String fullName;
    private String email;
    // Геттеры и сеттеры...
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }                    

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