✔️ Mapper을 구현하기 위해 MapStruct를 사용하면서 새롭게 알게 된 사실들을 정리하려고 한다.
✅ Mapper 구현 이유
- DTO 클래스와 Entity 클래스의 변환
- 코드 단순화
- 계층간의 역할 분리
- REST API 독립성 확보
✅ MapStruct
- Mapper를 지원해주는 라이브러리
- gradle.yml 파일에서 아래를 추가
dependencies {
...
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
// 버전은 바뀔수도 있음.
✔️ Check Mistake
- annotationProcessor를 추가하지 않고 작업을 하다가 에러를 만났다.
- 처음에는 MapperImple 생성자가 없다며 설정하라는 에러가 나타났다.
- 하지만 나는 @Builder를 사용했기 때문에 에러가 나면 안되는 상황이었다.
- 그 다음에는 아래의 있는 코드 에러가 났다.
- 다 바꿔보았는데 해결이 되지 않아, gradle.yml 파일을 확인하니 annotationProcessor가 추가되지 않았다...(머쓱타드)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException at DefaultListableBeanFactory.java:1801
✅ MapStruct를 사용하여 MapperImple 자동 구현
- MapStruct에 있는 @Mapper 어노테이션을 이용하면 쉽게 MapperImple를 구현 할 수 있다.
- ✨ 애트리뷰트를 componetModel = "spring" 으로 설정해주면 SpringBean으로 등록이 된다. ✨
- MapperImple 클래스에 @Componet 어노테이션이 붙는다!
@Mapper(componentModel = "spring")
public interface BoardMapper {
Board boardPostDtoToBoard(BoardDto.Post boardPostDto);
Board boardPatchDtoToBoard(BoardDto.Patch boardPatchDto);
BoardDto.Response boardToBoardResponseDto(Board board);
List<BoardDto.Response> boardToBoardResponseDtos(List<Board> boards);
}
✔️ Check Point
- DTO의 내부 클래스에서 Lombok 어노테이션에 따라 MapperImple의 세부 구성이 달라진다.
1️⃣ PostDto, PatchDto
- Getter 어노테이션을 사용했기 때문에, 필드를 읽어서 set() 메서드로 값을 넣어준다.
public class BoardDto {
@Getter
public static class Post{
@NotBlank
private String writer;
@NotBlank
private String comment;
}
...
}
@Component
public class BoardMapperImpl implements BoardMapper {
public BoardMapperImpl() {
}
public Board boardPostDtoToBoard(BoardDto.Post boardPostDto) {
if (boardPostDto == null) {
return null;
} else {
Board board = new Board();
board.setWriter(boardPostDto.getWriter());
board.setComment(boardPostDto.getComment());
return board;
}
}
public Board boardPatchDtoToBoard(BoardDto.Patch boardPatchDto) {
if (boardPatchDto == null) {
return null;
} else {
Board board = new Board();
board.setBoardId(boardPatchDto.getBoardId());
board.setWriter(boardPatchDto.getWriter());
board.setComment(boardPatchDto.getComment());
board.setBoardStatus(boardPatchDto.getBoardStatus());
return board;
}
}
...
}
2️⃣ ResponseDto
- 이 MapperImple를 여러번 시도해보게 된 이유다.
- Lombok 어노테이션을 어떻게 사용하느냐에 따라 구현이 달라진다.
- 아래의 우선 조건을 확인해보기 위해 시도해보았다.
⭐️❗️MapStruct가 Mapping을 정상적으로 하기 위한 우선 조건❗️⭐️
(1번부터 우선 순위가 높고, 우선 순위가 높은 조건에 먼저 일치하면 해당 조건으로 매핑을 진행)
- Builder 패턴이 적용되어 있는 경우
- 모든 필드를 파라미터로 갖는 생성자가 있는 경우 (단, 기본 생성자가 포함되어 있을 경우 2의 생성자는 역할을 못함.)
- Setter 메서드가 있는 경우
✔️@Getter + @Builder
- @Builder : 빌더 패턴 코드 빌드
public class BoardDto {
...
@Getter
@Builder
public static class Response{
private long boardId;
private String writer;
private String comment;
private Board.BoardStatus boardStatus;
private String getBoardStatus(){return boardStatus.getStatus();}
}
}
@Component
public class BoardMapperImpl implements BoardMapper {
...
public BoardDto.Response boardToBoardResponseDto(Board board) {
if (board == null) {
return null;
} else {
// builder()
BoardDto.Response.ResponseBuilder response = Response.builder();
response.boardId(board.getBoardId());
response.writer(board.getWriter());
response.comment(board.getComment());
response.boardStatus(board.getBoardStatus());
return response.build();
}
}
...
}
✔️ @Getter + @AllArgsConstructor
- @Getter : get 메서드를 생성
- @AllArgsConstructor : 모든 필드를 파라미터로 받는 생성자 생성
public class BoardDto {
...
@Getter
@AllArgsConstructor
public static class Response{
private long boardId;
private String writer;
private String comment;
private Board.BoardStatus boardStatus;
private String getBoardStatus(){return boardStatus.getStatus();}
}
}
@Component
public class BoardMapperImpl implements BoardMapper {
...
public BoardDto.Response boardToBoardResponseDto(Board board) {
if (board == null) {
return null;
} else {
// 변수 선언 및 초기화
long boardId = 0L;
String writer = null;
String comment = null;
Board.BoardStatus boardStatus = null;
// 변수에 값 할당
boardId = board.getBoardId();
writer = board.getWriter();
comment = board.getComment();
boardStatus = board.getBoardStatus();
// 모든 필드를 파라미터로 받는 생성자 생성하여, 파라미터를 넣어줌.
BoardDto.Response response = new BoardDto.Response(boardId, writer,
comment, boardStatus);
return response;}
}
}
✔️@Getter + @Setter + @NoArgsConstructor
- @Setter : set 메서드를 생성
- @NoArgsConstructor : 파라미터가 없는 기본 생성자를 생성
public class BoardDto {
...
@Getter
@Setter
@NoArgsConstructor
public static class Response{
private long boardId;
private String writer;
private String comment;
private Board.BoardStatus boardStatus;
private String getBoardStatus(){return boardStatus.getStatus();}
}
}
@Component
public class BoardMapperImpl implements BoardMapper {
...
public BoardDto.Response boardToBoardResponseDto(Board board) {
if (board == null) {
return null;
} else {
// 기본 생성자 생성
BoardDto.Response response = new BoardDto.Response();
// setter 메서드로 호출하여 값을 넣어줌.
response.setBoardId(board.getBoardId());
response.setWriter(board.getWriter());
response.setComment(board.getComment());
response.setBoardStatus(board.getBoardStatus());
return response;
}
}
}
'Spring' 카테고리의 다른 글
[Spring] 예외처리 - 공통화(@RestControllerAdvice) (0) | 2022.10.27 |
---|---|
[Spring] 예외 처리 - Controller에서 처리 (0) | 2022.10.27 |
[Spring Framework] ResponseEntity (0) | 2022.08.25 |
[Spring] Spring MVC Controller 어노테이션 (0) | 2022.08.24 |
[Spring] Spring MVC (0) | 2022.08.24 |
댓글