Files
spring-boot-examples/docs/25_map struct.md
2025-04-08 19:56:24 +09:00

367 lines
8.0 KiB
Markdown

# MapStruct란?
**MapStruct**는 Java Bean 간의 매핑을 쉽게 해주는 코드 생성 기반 매퍼 프레임워크입니다.
DTO(Data Transfer Object)와 Entity 간의 변환을 할 때 반복되는 getter/setter 작업을 자동화해주며, **런타임이 아닌 컴파일 타임에 매핑 코드를 생성**하므로 성능도 뛰어납니다.
## 주요 특징
- **컴파일 타임에 코드 생성** (런타임 Reflection 사용 X)
- Lombok과 잘 연동됨
- 커스터마이징 가능 (부분 매핑, `@Mapping` 등)
- Spring과 쉽게 통합
---
## Spring Boot에서 MapStruct 사용 예시
### 1. Gradle 설정
```kotlin
dependencies {
implementation ("org.mapstruct:mapstruct:1.6.3")
annotationProcessor ("org.mapstruct:mapstruct-processor:1.6.3")
}
tasks.withType(JavaCompile) {
options.annotationProcessorPath = configurations.annotationProcessor
}
```
---
### 2. DTO 및 Entity 클래스
```java
// User.java (Entity)
@Getter @Setter
public class User {
private Long id;
private String name;
private String email;
}
```
```java
// UserDto.java
@Getter @Setter
public class UserDto {
private String name;
private String email;
}
```
---
### 3. 매퍼 인터페이스 작성
```java
@Mapper(componentModel = "spring") // Spring Bean으로 등록
public interface UserMapper {
UserDto toDto(User user);
User toEntity(UserDto userDto);
}
```
> `componentModel = "spring"`을 설정하면 Spring의 `@Component`로 등록되므로, 의존성 주입이 가능합니다.
---
### 4. 서비스에서 사용
```java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
public UserDto getUserDto(User user) {
return userMapper.toDto(user);
}
}
```
---
## 마무리
MapStruct는 반복적인 매핑 코드를 없애고, 명시적이고 빠른 매핑을 제공하는 훌륭한 도구입니다. 특히 **Spring Boot + Lombok**과 함께 사용하면 생산성과 유지보수성이 크게 향상됩니다.
필요하다면 `@Mapping` 어노테이션을 사용해 필드명을 다르게 매핑하거나, 일부 필드만 매핑하는 커스터마이징도 가능합니다.
---
좋습니다! 이번에는 **MapStruct의 고급 기능**들을 소개해드릴게요. 실무에서 자주 쓰이는 매핑 커스터마이징 기능과 예제 중심으로 설명드릴게요.
---
## MapStruct 고급 기능
### 1. **@Mapping**: 이름이 다른 필드 매핑
DTO와 Entity의 필드명이 다를 경우 사용합니다.
```java
@Getter @Setter
public class User {
private Long id;
private String fullName;
}
```
```java
@Getter @Setter
public class UserDto {
private String name;
}
```
```java
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mapping(source = "fullName", target = "name")
UserDto toDto(User user);
}
```
> `source`: 원본 객체의 필드
> `target`: 매핑될 대상 객체의 필드
---
### 2. **@Mapping(target = ..., ignore = true)**: 특정 필드 무시
```java
@Mapping(target = "id", ignore = true)
User toEntity(UserDto dto);
```
`id`는 DB에서 자동 생성되므로 무시할 때 자주 사용합니다.
---
### 3. **@InheritInverseConfiguration**: 역 매핑 정의 재사용
```java
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mapping(source = "fullName", target = "name")
UserDto toDto(User user);
@InheritInverseConfiguration
User toEntity(UserDto dto);
}
```
> `toDto`에 정의된 매핑 규칙을 `toEntity`에도 반대로 적용합니다.
---
### 4. **@AfterMapping / @BeforeMapping**: 매핑 전후 로직 삽입
```java
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDto toDto(User user);
@AfterMapping
default void enrichDto(@MappingTarget UserDto dto) {
dto.setName(dto.getName().toUpperCase()); // 예: 이름을 대문자로 처리
}
}
```
---
### 5. **중첩 객체 매핑 (Nested Mapping)**
```java
@Getter @Setter
public class User {
private Address address;
}
@Getter @Setter
public class Address {
private String city;
}
```
```java
@Getter @Setter
public class UserDto {
private String city;
}
```
```java
@Mapping(source = "address.city", target = "city")
UserDto toDto(User user);
```
> 중첩된 객체의 필드도 `. (dot)` 문법으로 접근 가능합니다.
---
### 6. **Collection 매핑**
```java
List<UserDto> toDtoList(List<User> users);
Set<User> toEntitySet(Set<UserDto> dtos);
```
> List, Set, Map 같은 컬렉션도 자동 매핑됩니다.
---
### 7. **Mapper 간 의존성 (다른 Mapper 사용)**
```java
@Mapper(componentModel = "spring", uses = { AddressMapper.class })
public interface UserMapper {
UserDto toDto(User user);
}
```
> 복잡한 구조에서는 하위 매퍼를 만들어 분리할 수 있어요.
---
## 정리
MapStruct 고급 기능을 사용하면 다음과 같은 작업이 쉬워집니다:
- 필드명이 다른 경우 매핑
- 무시할 필드 지정
- 매핑 재사용 (`@InheritInverseConfiguration`)
- 후처리 로직 삽입
- 복잡한 구조/컬렉션/다형성 처리
---
좋아요! 이번에는 **MapStruct의 고급 기능** 중에서도 실무에서 자주 사용되는 **Enum 매핑**, **커스텀 매핑 메서드**, **Builder 패턴 지원**에 대해 자세히 설명드릴게요.
---
## 1. Enum 매핑
MapStruct는 기본적으로 **같은 이름을 가진 Enum 상수끼리 자동으로 매핑**합니다.
### 예시: 이름이 같은 Enum 매핑
```java
public enum Role {
ADMIN, USER
}
public enum RoleDto {
ADMIN, USER
}
```
```java
@Mapper(componentModel = "spring")
public interface UserMapper {
RoleDto toDto(Role role);
}
```
> 이름이 같다면 자동으로 매핑됩니다.
> 이름이 다르면 `@ValueMapping` 또는 `@Mapping`을 사용해야 해요.
### 예시: 이름이 다른 Enum 매핑
```java
public enum Role {
ADMINISTRATOR, NORMAL_USER
}
public enum RoleDto {
ADMIN, USER
}
```
```java
@ValueMappings({
@ValueMapping(source = "ADMINISTRATOR", target = "ADMIN"),
@ValueMapping(source = "NORMAL_USER", target = "USER")
})
RoleDto toDto(Role role);
```
---
## 2. 커스텀 매핑 메서드
MapStruct는 매핑 중 호출할 **사용자 정의 메서드**도 지원합니다.
### 예시: 날짜 형식 변환
```java
@Getter @Setter
public class User {
private LocalDateTime registeredAt;
}
@Getter @Setter
public class UserDto {
private String registeredDate;
}
```
```java
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mapping(source = "registeredAt", target = "registeredDate")
UserDto toDto(User user);
default String map(LocalDateTime dateTime) {
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
}
}
```
> MapStruct는 `LocalDateTime -> String` 변환 시 `map(...)` 메서드를 자동으로 호출합니다.
---
## 3. Builder 패턴 지원
Builder 패턴을 사용하는 객체도 MapStruct에서 지원합니다.
Lombok의 `@Builder`를 쓰거나 직접 Builder 클래스를 만든 경우에도 동작합니다.
### 예시: Lombok + Builder
```java
@Getter @Setter
@Builder
public class UserDto {
private String name;
private String email;
}
```
```java
@Mapper(componentModel = "spring", builder = @Builder(disableBuilder = false))
public interface UserMapper {
UserDto toDto(User user);
}
```
> MapStruct는 `@Builder`가 붙은 클래스에 대해 자동으로 `.builder().field(...).build()`를 사용해 객체를 생성합니다.
### 주의할 점:
- `@Builder(disableBuilder = true)`이면 Builder 무시
- `@Builder(disableBuilder = false)` 또는 생략하면 Builder 사용
- 커스텀 Builder 클래스의 경우 구조가 달라지면 빌더 매핑이 안 될 수도 있으니 주의
---
## 마무리 정리
| 기능 | 설명 |
|------|------|
| **Enum 매핑** | Enum 이름 자동 매핑, 다를 경우 `@ValueMapping` 사용 |
| **커스텀 메서드** | 복잡한 변환 로직을 메서드로 정의 가능 |
| **Builder 패턴** | `@Builder` 객체도 매핑 가능 (자동 빌더 생성) |
---