
Spring Boot 프로젝트 중 객체 간의 매핑을 도와주는
MapStruct 를 활용하자.
간단한 설정으로 entity <-> dto 매핑을 도와줌으로써 보일러 플레이트를 해결해 준다!
💡 ex) 요구 사항
게시글 CRUD 중 ....
게시글의ID,제목,내용을 포함해서Response한다.
위의 요구 사항을 만족하기 위한 응답으로,
Entity 대신 DTO 로 응답하는 이유가 있다.
보안 문제
Entity는 민감한 정보를 포함, 노출 시 보안 취약점이 발생할 수 있음.의미적 분리
Entity데이터베이스의 구조를 반영하고 비즈니스 로직에 종속적일 수 있음.
API 응답은 클라이언트와의 의사소통을 위해 조정된 데이터 구조가 필요하며
Entity를 그대로 반환하면 클라이언트에게 불필요한 정보가 노출될 수 있음.불필요한 데이터 전송 최소화
클라이언트가 필요한 데이터만 전송할 수 있음.
변경 용이성
Entity의 구조를 변경할 경우, API 응답도 같이 변경됨.
직접 반환하는 경우 클라이언트에게 영향을 미침.버전 관리
API 응답의 구조를 명시적으로 관리함으로써 더 유연한 버전관리가 가능함.
위의 이유가 전부는 아니겠지만 Entity 대신 DTO 를 활용하여 클라이언트에게 응답해보자!
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
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SamplePostEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
}
간단한 게시글이다.
Id, Title, Content 3개의 필드를 가지고 있는 Entity 다.
💡 클라이언트의 요청으로 Title, Content 2개의 응답을 원한다고 가정하자!!
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 {
}
}
자바 14에서 등장한 record 를 활용하여 DTO 를 만들자.
데이터를 다룰 때 적합한 것 같다
게시글 생성 요청에 대한 RequestDTO 와 생성 응답에 대한 ResponseDTO 2개만 있다.
간단하게 테스트한다.
🎯
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
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<> 만 붙여주면 된다.
ex)
List<PostResponseDTO.Get> postToPostListDTO(List<Post> posts);
Entity 의 필드명을 그대로 가져와서 DTO 로 담으면 쉽게 매핑이 가능한 것 같다.
여러개의 매개변수를 받는 경우에도 매핑을 잘 해주면 된다.