← Назад к вопросам

Что такое маппинг?

2.3 Middle🔥 121 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Маппинг: преобразование данных между слоями приложения

Маппинг — это процесс преобразования данных из одного формата в другой. В Java приложениях это один из самых распространённых паттернов, который применяется в многослойной архитектуре для преобразования данных между слоем доступа к БД, бизнес-логикой и представлением.

Типы маппинга

1. Маппинг Entity ↔ DTO

Моделирует преобразование между Entity (объект из БД) и DTO (Data Transfer Object, объект передачи данных).

// Entity из базы данных
@Entity
public class UserEntity {
    @Id
    private Long id;
    private String email;
    private String passwordHash;
    private LocalDateTime createdAt;
    @OneToMany(mappedBy = "user")
    private List<OrderEntity> orders;
}

// DTO для API ответа
public class UserDto {
    private Long id;
    private String email;              // Не отправляем пароль!
    private LocalDateTime createdAt;
    private List<OrderDto> orders;     // Вложенные DTO
}

Причины маппинга:

  • Скрыть чувствительные данные (пароли, внутренние ID)
  • Избежать проблемы N+1 queries (загружаем только нужные связи)
  • Разделить слои (БД не знает о REST API)
  • Валидация данных перед отправкой

2. ORM маппинг (Object-Relational Mapping)

Преобразование между объектами Java и таблицами БД.

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;           // Поле → Колонка id
    
    @Column(name = "user_email")
    private String email;      // Поле → Колонка user_email
    
    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department dept;   // Связь → Внешний ключ
}

Hibernate/JPA автоматически преобразуют:

  • Java объекты в SQL запросы
  • Результаты запроса в объекты
  • Типы данных (Date ↔ TIMESTAMP, List ↔ JOIN)

3. Маппинг JSON ↔ Java объекты

Преобразование между JSON (от клиента) и Java объектами.

// Входящий JSON
// {"email": "user@example.com", "age": 30}

public class CreateUserRequest {
    @NotBlank
    private String email;
    
    @Min(18)
    private int age;
}

// Автоматический маппинг через Jackson
@PostMapping("/users")
public void createUser(@RequestBody CreateUserRequest request) {
    // request уже замаппирован из JSON
}

Инструменты маппинга

1. MapStruct — скомпилированный маппинг

@Mapper
public interface UserMapper {
    UserDto toDto(UserEntity entity);
    UserEntity toEntity(UserDto dto);
    
    @Mapping(source = "departmentId", target = "dept.id")
    @Mapping(target = "password", ignore = true)  // Игнорируем пароль
    UserDto toDtoWithDept(UserEntity entity);
}

// Использование
UserDto dto = userMapper.toDto(userEntity);

Преимущества:

  • Самый быстрый (компилируется в обычный Java код)
  • Типобезопасность на этапе компиляции
  • Нет рефлексии, минимальные сверху
  • Логирование проблем на этапе компиляции

2. ModelMapper — конвенции по умолчанию

ModelMapper mapper = new ModelMapper();

// Автоматический маппинг по названиям полей
UserDto dto = mapper.map(userEntity, UserDto.class);

// Кастомизация
mapper.createTypeMap(UserEntity.class, UserDto.class)
    .addMapping(UserEntity::getPasswordHash, (dest, v) -> dest.setPassword(null))
    .addMapping(src -> src.getDept().getId(), UserDto::setDeptId);

Преимущества:

  • Простота использования
  • Минимум конфигурации
  • Хорошо для простых случаев

3. Dozer — XML конфигурация

<mappings>
    <mapping>
        <class-a>com.example.UserEntity</class-a>
        <class-b>com.example.UserDto</class-b>
        <field>
            <a>id</a>
            <b>id</b>
        </field>
        <field>
            <a>email</a>
            <b>email</b>
        </field>
    </mapping>
</mappings>

4. Ручной маппинг

public class UserMapper {
    public static UserDto toDto(UserEntity entity) {
        if (entity == null) return null;
        
        UserDto dto = new UserDto();
        dto.setId(entity.getId());
        dto.setEmail(entity.getEmail());
        // Не копируем пароль!
        return dto;
    }
}

Когда использовать:

  • Очень сложная логика преобразования
  • Особые правила валидации
  • Полный контроль над процессом

Практические примеры из моей практики

Пример 1: API с Entity маппингом

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    @Autowired
    private UserMapper userMapper;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        UserEntity entity = userService.findById(id);
        UserDto dto = userMapper.toDto(entity);  // Маппинг
        return ResponseEntity.ok(dto);
    }
    
    @PostMapping
    public ResponseEntity<UserDto> createUser(@RequestBody CreateUserRequest request) {
        // 1. Преобразуем request в entity
        UserEntity entity = userMapper.toEntity(request);
        // 2. Сохраняем в БД
        UserEntity saved = userService.save(entity);
        // 3. Возвращаем DTO
        return ResponseEntity.status(201).body(userMapper.toDto(saved));
    }
}

Пример 2: Вложенный маппинг с коллекциями

@Mapper
public interface OrderMapper {
    @Mapping(source = "customer.id", target = "customerId")
    @Mapping(source = "items", target = "items")
    OrderDto toDto(OrderEntity entity);
    
    // Для коллекций
    @IterableMapping(elementTargetType = OrderDto.class)
    List<OrderDto> toDtoList(List<OrderEntity> entities);
}

// Использование
List<OrderDto> orders = orderMapper.toDtoList(orderEntities);

Пример 3: Условный маппинг

public UserDto toDtoWithOrders(UserEntity entity, boolean includeOrders) {
    UserDto dto = mapBasicUser(entity);
    
    if (includeOrders) {
        dto.setOrders(entity.getOrders().stream()
            .map(this::orderToDto)
            .collect(Collectors.toList()));
    }
    
    return dto;
}

Расчёт производительности маппинга

В высоконагруженных системах маппинг может стать узким местом:

// Профилирование времени маппинга
long start = System.nanoTime();
for (int i = 0; i < 100000; i++) {
    userMapper.toDto(userEntity);
}
long elapsed = System.nanoTime() - start;
System.out.println("Среднее время: " + (elapsed / 100000 / 1000) + "мкс");

MapStruct: ~1-2 микросекунды Jackson: ~3-5 микросекунд ModelMapper: ~50-100 микросекунд Ручной маппинг: ~0.5-1 микросекунда

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

  1. Используй MapStruct для production приложений (лучшая производительность)
  2. Маппи в пограничных слоях (контроллеры, репозитории)
  3. Не передавай Entity наружу приложения
  4. Используй DTO для разных представлений одних данных
  5. Кэшируй маппинг если приходится преобразовывать одни и те же данные много раз
Что такое маппинг? | PrepBro