[MapStruct] 객체 간의 매핑
[MapStruct] 객체 간의 매핑

[MapStruct] 객체 간의 매핑

Tags
Spring
Core
DTO
Entity
MapStruct
Published
November 18, 2023
Author
lkdcode
Spring Boot 프로젝트 중 객체 간의 매핑을 도와주는MapStruct 를 활용하자.
간단한 설정으로 entity <-> dto 매핑을 도와줌으로써 보일러 플레이트를 해결해 준다!
 
💡 ex) 요구 사항
게시글 CRUD 중 ....
게시글의 ID제목내용 을 포함해서 Response 한다.
 
위의 요구 사항을 만족하기 위한 응답으로,
Entity 대신 DTO 로 응답하는 이유가 있다.
 
보안 문제
Entity 는 민감한 정보를 포함, 노출 시 보안 취약점이 발생할 수 있음.
 

의미적 분리

Entity 데이터베이스의 구조를 반영하고 비즈니스 로직에 종속적일 수 있음.
API 응답은 클라이언트와의 의사소통을 위해 조정된 데이터 구조가 필요하며
Entity 를 그대로 반환하면 클라이언트에게 불필요한 정보가 노출될 수 있음.
 

불필요한 데이터 전송 최소화

클라이언트가 필요한 데이터만 전송할 수 있음.
 

변경 용이성

Entity 의 구조를 변경할 경우, API 응답도 같이 변경됨.
직접 반환하는 경우 클라이언트에게 영향을 미침.
 

버전 관리

API 응답의 구조를 명시적으로 관리함으로써 더 유연한 버전관리가 가능함.
 
위의 이유가 전부는 아니겠지만 Entity 대신 DTO 를 활용하여 클라이언트에게 응답해보자!

Dependencies

Gradle 의존성을 추가해주자. Lombok 아래에 추가를 해주자.
// --------- mapper implementation 'org.mapstruct:mapstruct:1.5.3.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
 

게시글 Entity

@Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class SamplePostEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String content; }
간단한 게시글이다.
IdTitleContent 3개의 필드를 가지고 있는 Entity 다.
 
💡 클라이언트의 요청으로 Title, Content 2개의 응답을 원한다고 가정하자!!
 

게시글 DTO

public sealed interface SamplePostDTO permits CreateResponseDTO, CreateRequestDTO { @Builder record CreateResponseDTO( String DTOtitle, String DTOcontent ) implements SamplePostDTO { } @Builder record CreateRequestDTO( String DTOtitle, String DTOcontent ) implements SamplePostDTO { } }
  1. 자바 14에서 등장한 record 를 활용하여 DTO 를 만들자.
    1. 데이터를 다룰 때 적합한 것 같다
  1. 게시글 생성 요청에 대한 RequestDTO 와 생성 응답에 대한 ResponseDTO 2개만 있다.
 

Controller, Service...

간단하게 테스트한다.
🎯 Controller 의 코드다
@RestController @RequestMapping("/api/sample-posts") @RequiredArgsConstructor public class SampleController { private final SampleService sampleService; @PostMapping public SamplePostDTO.CreateResponseDTO sampleCreate(@RequestBody SamplePostDTO.CreateRequestDTO dto) { return sampleService.sampleMethod(dto); } }
🎯 Service 의 코드다
@Service public class SampleService { public SamplePostDTO.CreateResponseDTO sampleMethod(SamplePostDTO.CreateRequestDTO dto) { SamplePostEntity entity = SamplePostMapper.INSTANCE.createRequestDTOToEntity(dto); /* * 로직... */ return SamplePostMapper.INSTANCE.entityToCreateResponseDTO(entity); } }
맵핑만 되는지 확인을 위해 다른 로직은 제거한 상태다.

# Mapper Interface

매핑을 위한 맵퍼 코드다.
@Mapper public interface SamplePostMapper { SamplePostMapper INSTANCE = Mappers.getMapper(SamplePostMapper.class); @Mapping(source = "DTOtitle", target = "title") @Mapping(source = "DTOcontent", target = "content") SamplePostEntity createRequestDTOToEntity(SamplePostDTO.CreateRequestDTO dto); @Mapping(source = "title", target = "DTOtitle") @Mapping(source = "content", target = "DTOcontent") SamplePostDTO.CreateResponseDTO entityToCreateResponseDTO(SamplePostEntity entity); }
@Mapping 어노테이션을 붙여주고 매핑할 필드를 적어준다.
source : 매개변수로 넘어오는 필드값이다.
target : 변환할 객체의 필드값이다.
@Mapping(source = "DTOtitle", target = "title") @Mapping(source = "DTOcontent", target = "content") SamplePostEntity createRequestDTOToEntity(SamplePostDTO.CreateRequestDTO dto);
위의 코드는 dto 로 entity 를 매핑한다.
SamplePostDTO.CreateRequestDTO dto 의 String DTOtitle 을
SamplePostEntity 의 String title 로 매핑한다.
SamplePostDTO.CreateRequestDTO dto 의 String DTOcontent 를
SamplePostEntity 의 String content 로 매핑한다.
💡 만약, source = "title" 와 target = "title" 의 필드명이 같다면 일일이 다 매핑해줄 필요없다.
dto 의 필드명을 entity 와 통일 시키면 Mapping 어노테이션 1개만 해도 나머지는 알아서 매핑이 된다.
 

List 매핑

여러개의 게시글을 불러오는 경우에도 매핑이 가능하다.
이미 매핑이 되어 있다면 List<> 만 붙여주면 된다.
ex)
List<PostResponseDTO.Get> postToPostListDTO(List<Post> posts);
Entity 의 필드명을 그대로 가져와서 DTO 로 담으면 쉽게 매핑이 가능한 것 같다.
여러개의 매개변수를 받는 경우에도 매핑을 잘 해주면 된다.