목차
📚 MapStruct 시리즈
- 기본 사용법 <- now
- Default 메서드 활용한 커스텀 매핑 전략
- @MappingTarget: 기존 객체 수정 매핑
1. MapStruct란?
- DTO와 Entity 간 매핑을 자동화해주는 Java 어노테이션 기반 코드 생성기
- 반복적인 setter, builder 코드 제거
- @Mapping, @Mapper 등의 어노테이션 기반으로 설정
2. Why DTO?
Entity ↔ JSON 변환 시 생기는 문제점
| 문제점 | 설명 |
| 보안 이슈 | 비밀번호, 내부 ID 등 민감한 정보 노출 위험 |
| 유연성 저하 | Entity 변경 시 API 응답/요청도 영향받음 |
| 캡슐화 위반 | DB와 API 설계가 강하게 결합됨 |
| Validation 혼란 | 입력 값 검증 로직이 분리되기 어려움 |
DTO ← (MapStruct) → Entity
- Controller: DTO로 요청 받고 응답 줌
- Service: Entity로 로직 수행
- MapStruct: DTO ↔ Entity 변환 담당 (mapper 인터페이스)
3. 매핑 전략
- 필드 이름이 같으면 → 자동 매핑
- 필드 이름이 다르면 → @Mapping 어노테이션 활용
- 예: @Mapping(source = "username", target = "name")
- 중첩된 객체도 지원: @Mapping(source = "stamp.count", target = "stampCount")
4. 생성자 우선순위
- 빌더 패턴: @Builder
- 전체 생성자: @AllargsConstructor
- 기본 생성자 + Setter
5. build.gradle 설정
dependencies {
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
}
6. 정리
- DTO는 Entity와 API 간 중간 다리 역할
- MapStruct는 그 사이의 변환을 자동화하는 도구
- 깔끔한 계층 분리를 유지하면서도 개발 속도, 안정성을 높여줌
- 실무에서는 단순 매핑에 MapStruct 자주 사용
- 복잡하거나 조건 있는 매핑은 커스터마이징하거나 수동 매퍼 필요
7. 코드로 보는 MapStruct
// 이 매퍼를 Spring Bean으로 등록해서 DI를 받을 수 있게 해주는 어노테이션
@Mapper(componentModel = "spring")
public interface MemberMapper {
// MemberPostDto → Member 매핑 (POST 요청용)
// username 필드를 Member의 name으로 매핑
// phoneNumber 필드를 Member의 phone으로 매핑
@Mapping(source = "username", target = "name")
@Mapping(source = "phoneNumber", target = "phone")
Member memberPostDtoToMember(MemberPostDto memberPostDto);
// Member 리스트 → MemberResponseDto 리스트 매핑 (조회 시 응답용)
List<MemberResponseDto> membersToMemberResponseDtos(List<Member> members);
// Member → MemberResponseDto 매핑 (단건 조회 시 응답용)
// Member 내 Stamp 엔티티의 count 값을 ResponseDto의 stampCount로 매핑
@Mapping(source = "stamp.count", target = "stampCount")
MemberResponseDto memberToMemberResponseDto(Member member);
}
@Getter
@AllArgsConstructor
@Builder
public class MemberPostDto {
private String email;
private String username;
private String phoneNumber;
}
//@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
//@Builder
public class MemberResponseDto {
private long memberId;
private String email;
private String name;
private String phone;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
private List<OrderResponseDto> orders;
private int stampCount;
}
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false, updatable = false, unique = true)
private String email;
// (1)
@Column(length = 100, nullable = false)
private String name;
@Column(length = 13, nullable = false, unique = true)
private String phone;
@Column(nullable = false)
private LocalDateTime createdAt = LocalDateTime.now(); // (2)
// (3)
@Column(nullable = false, name = "LAST_MODIFIED_AT")
private LocalDateTime modifiedAt = LocalDateTime.now();
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
@OneToOne(mappedBy = "member", cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
private Stamp stamp;
728x90
'Backend Development > SPRING' 카테고리의 다른 글
| [MapStruct] Default 메서드 활용한 커스텀 매핑 전략 (1) | 2025.07.23 |
|---|---|
| N+1 문제의 발생 원인과 해결 방안 (5) | 2025.07.23 |
| [JPA] 어노테이션 정리 (1) | 2025.07.21 |
| JPA & Spring Data JPA (0) | 2025.07.19 |
| Framework, WAS, Bean 등록 (3) | 2025.06.28 |