사용자가 입력한 데이터가 담긴 DTO를 DB에 저장할 Entity로 바꾸기 위한, mapstruct 사용법에 대해 정리해보자.
MapStruct 란 ?
프로젝트를 할 때, DTO 와 Entity 를 매핑해야하는 경우가 많은데 이러한 매핑 코드를 작성하는 것은 귀찮고 오류가 발생하기 쉽다. MapStruct 는 이를 자동화하여 작업을 단순화할 수 있게 한다.
자세한 내용은, 아래 공식 문서를 통해 확인할 수 있다.
MapStruct – Java bean mappings, the easy way!
Java bean mappings, the easy way! Get started Download
mapstruct.org
MapStruct 사용하기
1. dependency 추가 및 주의사항( Gradle )
dependencies {
	..
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	implementation 'org.mapstruct:mapstruct:1.5.5.Final'
	annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
}그냥 dependency 를 추가하면 되는데 주의할 점은 lombok 과 같이 사용하는 경우이다.
MapStruct가 Lombok 빌더 또는 Lombok Setter를 사용하려는 경우 Lombok이 아직 실행되지 않은 경우 컴파일이 실패하여 문제가 발생할 수 있다.
간단한 방법은 위와 같이 lombok -> mapstruct 순서로 나열하는 것이다. 혹은, MapStruct 의 github을 참조할 수 있다.
2. DTO 및 Entity 생성
- Member
@Getter
public class Member {
    private Long id;
    private String email;
    private String passwd;
    private String name;
    private String delYN;
    @Builder
    public Member(String email, String passwd, String name) {
        this.email = email;
        this.passwd = passwd;
        this.name = name;
    }
}- MemberDTO
@Getter
public class MemberDTO {
    private String email;
    private String passwd;
    private String name;
    @Builder
    public MemberDTO(String email, String passwd, String name) {
        this.email = email;
        this.passwd = passwd;
        this.name = name;
    }
}필수적인 요소만 남겨뒀다.
인터페이스를 생성하여 원하는 기능을 구현할텐데, MapStruct에서 구현체를 자동으로 만드는데 Getter 및 Builder 를 사용하기 때문에 해당 내용까지 적어뒀다. 필요에 따라 validation 등의 어노테이션을 추가할 수 있다.
3. Mapper 인터페이스 구현
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MemberMapper {
    MemberDTO toDto(Member member);
    Member toEntity(MemberDTO memberDTO);
    List<MemberDTO> toDtoList(List<Member> members);
    List<Member> toEntityList(List<MemberDTO> memberDTOS);
}필수 요소는,
- 인터페이스에 @Mapper를 사용하여 MapStruct를 사용할 수 있도록 한다.
- 바꾸고 싶은 결과를 return 값으로, 바꾸기 전 대상을 매개변수로 넣어 위와 같이 사용할 수 있다.
메서드명에 제약은 없으나 표준 및 가독성을 고려하여 자유롭게 구현하면 된다.
@Mapper 의 componentModel 옵션을 "spring" 으로 한 이유는, 구현체를 spring이 관리할 수 있도록 하기 위함이고,
unmappedTargetPolicy 옵션에는 IGNORE, WARN, ERROR 세 가지가 있는데 매핑되지 않는 속성값이 있을 때 어떻게 처리할 지에 대한 옵션이다.
각 DTO와 Entity 에서 필드 값은 다르지만 매핑시켜야 하는 경우에는, 아래와 같이 @Mapping 을 사용한다.
    @Mapping(source = "user_id", target = "email")
    MemberDTO toDto(Member member);source 가 매개변수로 들어간 Member, target이 결과값인 MemberDTO 의 속성 값이다.
다른 경우로는, 필드 값이 같은데 매핑시키고 싶지 않은 경우가 있을 것이다.
    @Mapping(source = "email",ignore = true)
    MemberDTO toDto(Member member);그럴 때는 source에 필드명을 적어주고 ignore 옵션을 true로 설정하면 된다.
4. 구현체 생성
이제, build 를 해서 구현체만 확인하면 된다.

- 실행 결과

그러면 build > generated > .. > mapper 하위에 구현체가 만들어져 있는 것을 확인할 수 있다.
5. 테스트 코드 작성 및 검증
그러면 이제 마지막으로 테스트를 통해 매핑이 잘 되는 지 검증한다.
- 테스트 코드
@ExtendWith({SpringExtension.class, MockitoExtension.class})
class MemberMapperTest {
    @InjectMocks
    MemberMapperImpl memberMapperImpl;
    @Test
    void then_Mapper_thenSuccess(){
        Member user = Member.builder()
                .email("test@test.com")
                .passwd("test1")
                .name("홍길동")
                .build();
        MemberDTO memberDto = memberMapperImpl.toDto(user); // entity -> dto 변환
        Assertions.assertThat(memberDto.getEmail()).isEqualTo("test@test.com");
    }
}위의 코드는 Entity → DTO 로 매핑하는 toDTO 에 대한 테스트이다.
결과 값으로 반환받은 memberDto의 이메일과 user의 이메일을 비교하여 같은 지 확인한다.
- 실행 결과

테스트는 성공으로, 변환이 성공적으로 되었음을 알 수 있다.
정리
변환해야 하는 경우가 많아질수록 실수가 늘거나 귀찮아질 것 같을 때 MapStruct를 사용하여 익숙해져 보는 것이 좋을 것 같다.
참고
https://springframework.guru/using-mapstruct-with-project-lombok/
'Web' 카테고리의 다른 글
| [Spring] XSS Prevention (2) | 2023.07.31 | 
|---|---|
| [Spring] Unit Tests with Mockito ( in Service Layer ) (0) | 2023.07.25 | 
| [Spring] 라이브러리 버전 관리하기 ( Gradle ) (0) | 2023.07.06 | 
| [Spring] Exception Handling 에 대해서 (3) | 2023.07.03 | 
| [Spring] @Builder 와 Builder 패턴 (0) | 2023.06.25 | 

