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

Что такое Mapper для dto классов?

2.0 Middle🔥 111 комментариев
#ORM и Hibernate

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

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

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

Что такое Mapper для DTO классов?

Определение

Mapper — это компонент или утилита, которая преобразует данные из одного формата в другой. В контексте DTO (Data Transfer Objects), mapper отвечает за конвертирование данных между слоями приложения: преобразование сущностей (Entity) в DTO и наоборот.

Зачем нужны Mappers

Изоляция слоёв:

// ❌ БЕЗ mapper - database model утечёт в API
@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // Возвращаем сущность напрямую
        return userRepository.findById(id);
        // Проблема: password и другие поля видны клиенту!
    }
}

// ✅ С mapper - используем DTO
@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public UserDTO getUser(@PathVariable Long id) {
        User user = userRepository.findById(id);
        return mapper.toDTO(user); // Только нужные поля
    }
}

Структура

Entity (Database Model):

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
    
    private String name;
    private String email;
    private String password; // НЕ должен быть в API!
    private String phone;
    
    @OneToMany
    private List<Order> orders;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
}

DTO (Data Transfer Object):

public class UserDTO {
    private Long id;
    private String name;
    private String email; // БЕЗ password!
    private String phone;
    private Integer orderCount;
    
    // Конструкторы, getters, setters
}

Mapper:

public interface UserMapper {
    UserDTO toDTO(User user);
    User toEntity(UserDTO dto);
    List<UserDTO> toDTOList(List<User> users);
}

Способы реализации

1. Manual Mapper (вручную):

@Component
public class UserMapperImpl implements UserMapper {
    @Override
    public UserDTO toDTO(User user) {
        if (user == null) return null;
        
        UserDTO dto = new UserDTO();
        dto.setId(user.getId());
        dto.setName(user.getName());
        dto.setEmail(user.getEmail());
        dto.setPhone(user.getPhone());
        dto.setOrderCount(user.getOrders().size());
        
        return dto;
    }
    
    @Override
    public User toEntity(UserDTO dto) {
        if (dto == null) return null;
        
        User user = new User();
        user.setId(dto.getId());
        user.setName(dto.getName());
        user.setEmail(dto.getEmail());
        user.setPhone(dto.getPhone());
        
        return user;
    }
}

2. ModelMapper (библиотека):

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.1.1</version>
</dependency>
@Configuration
public class MapperConfig {
    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
}

@Component
public class UserMapperImpl implements UserMapper {
    @Autowired
    private ModelMapper modelMapper;
    
    @Override
    public UserDTO toDTO(User user) {
        return modelMapper.map(user, UserDTO.class);
    }
    
    @Override
    public User toEntity(UserDTO dto) {
        return modelMapper.map(dto, User.class);
    }
}

3. MapStruct (аннотации, код-генерирующая библиотека):

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.3.Final</version>
</dependency>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.3.Final</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
@Mapper(componentModel = "spring")
public interface UserMapper {
    @Mapping(target = "orderCount", expression = "java(user.getOrders().size())")
    UserDTO toDTO(User user);
    
    @Mapping(target = "orders", ignore = true)
    User toEntity(UserDTO dto);
    
    List<UserDTO> toDTOList(List<User> users);
}

// MapStruct генерирует реализацию автоматически!

4. Lombok (для простых случаев):

@Data
@Builder
public class UserDTO {
    private Long id;
    private String name;
    private String email;
    private String phone;
}

Полный пример с Spring Boot

1. Entity:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Column(nullable = false)
    private String password;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Order> orders = new ArrayList<>();
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
}

2. DTO:

public class UserDTO {
    private Long id;
    private String name;
    private String email;
    private Integer ordersCount;
    
    // Конструкторы, getters, setters
    public UserDTO() {}
    
    public UserDTO(Long id, String name, String email, Integer ordersCount) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.ordersCount = ordersCount;
    }
}

3. Mapper (MapStruct):

@Mapper(componentModel = "spring")
public interface UserMapper {
    @Mapping(source = "orders", target = "ordersCount", expression = "java(user.getOrders().size())")
    UserDTO toDTO(User user);
    
    @Mapping(target = "orders", ignore = true)
    @Mapping(target = "password", ignore = true)
    User toEntity(UserDTO dto);
    
    List<UserDTO> toDTOList(List<User> users);
}

4. Service:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private UserMapper userMapper;
    
    public UserDTO getUserById(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
        return userMapper.toDTO(user);
    }
    
    public List<UserDTO> getAllUsers() {
        List<User> users = userRepository.findAll();
        return userMapper.toDTOList(users);
    }
    
    public UserDTO createUser(UserDTO userDTO) {
        User user = userMapper.toEntity(userDTO);
        user.setPassword(encodePassword(userDTO.getPassword()));
        user.setCreatedAt(LocalDateTime.now());
        
        User savedUser = userRepository.save(user);
        return userMapper.toDTO(savedUser);
    }
}

5. Controller:

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }
    
    @GetMapping
    public ResponseEntity<List<UserDTO>> getAllUsers() {
        List<UserDTO> users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO) {
        UserDTO created = userService.createUser(userDTO);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

Сравнение подходов

Manual Mapper:
✅ Полный контроль
✅ Понятный код
❌ Много boilerplate
❌ Легко ошибиться

ModelMapper:
✅ Автоматический
✅ Меньше кода
❌ Медлительнее (рефлексия)
❌ Сложнее дебажить

MapStruct:
✅ Быстрый (генерирует код)
✅ Типобезопасный
✅ Мало boilerplate
❌ Requires annotation processing
✅ Лучший выбор для большых приложений

Лучшие практики

// ✅ 1. Используй DTO для входящих данных
@PostMapping
public ResponseEntity<UserDTO> create(@RequestBody UserCreateDTO dto) {
    // dto не содержит id, password и т.д.
}

// ✅ 2. Разные DTO для разных операций
public class UserCreateDTO { } // Без id
public class UserUpdateDTO { } // Без password
public class UserResponseDTO { } // Для ответа

// ✅ 3. Игнорируй чувствительные поля
@Mapping(target = "password", ignore = true)
User toEntity(UserDTO dto);

// ✅ 4. Используй @Mapping для сложной логики
@Mapping(target = "orderCount", expression = "java(user.getOrders().size())")
UserDTO toDTO(User user);

// ❌ 5. НЕ передавай сущности напрямую в API
// Всегда используй DTO!

Альтернативные подходы

Использование конструкторов:

public class UserDTO {
    // Конструктор для маппинга
    public UserDTO(User user) {
        this.id = user.getId();
        this.name = user.getName();
        this.email = user.getEmail();
        this.ordersCount = user.getOrders().size();
    }
}

// Использование
UserDTO dto = new UserDTO(user);

Использование статических методов:

public class UserDTO {
    public static UserDTO from(User user) {
        UserDTO dto = new UserDTO();
        dto.id = user.getId();
        dto.name = user.getName();
        // ...
        return dto;
    }
}

// Использование
UserDTO dto = UserDTO.from(user);

Итог

Mapper для DTO — это критичный паттерн в modern Java приложениях, который:

  • Изолирует слои приложения
  • Контролирует, какие данные видны клиенту
  • Облегчает тестирование
  • Упрощает рефакторинг

Для большинства проектов рекомендуется использовать MapStruct, так как он обеспечивает лучший баланс между скоростью, типобезопасностью и простотой использования.