스프링 프로젝트를 진행하다보면, 계층 간의 데이터를 전달하는데에 DTO를 많이 사용한다. 때로는 VO를 쓰기도 한다.
해당되는 데이터를 DTO로 만들어 줄 때, 주로 생성자나 Builder, 또는 따로 만들어둔 메소드를 통해 만들게 된다.
그러나, 서비스가 많아지게 된다면 DTO 또한 많아지게 되며 관리하기가 까다로워질 수도 있다.
이러한 문제를 해결하는데 도움을 주는 기술인 MapStruct에 대해 알아보는 시간을 가져본다.
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
Lombok을 먼저 의존성을 달아준 뒤, MapStruct 의존성 달기
unmmappedTargetPolicy
설정(선택)
ERROR
: 매핑 대상의 필드가 없는 경우, 매핑 코드 생성 시 error 가 발생
WARN
: 매핑 대상의 필드가 없는 경우, 빌드 시 warn 이 발생(기본값)
IGNORE
: 매핑 대상의 필드가 없는 경우 무시하고 매핑
@Mapper(unmmapedTargetPolicy = ReportingPolicy.{ERROR,WARN,IGNORE})
public interface MessageMapper {
MessageMapper INSTANCE = Mappers.getMapper(MessageMapper.class);
MessageBodyDto toMessageBodyDto(RequestDto requestDto);
}
nullValueMappingStrategy / nullValueIterableMappingStrategy
설정(옵션)
RETURN_NULL : source가 null 일 경우, target을 null 로 설정
RETURN_DEFAULT : source가 null 일 경우, default 값으로 설정
- iterable에는 collection이 매핑 되며, map은 빈 map 으로 매핑이 됩니다.
@Mapper(
nullValueMapMappingStrategy = NullValueMappingStrategy.{RETURN_NULL,RETURN_DEFAULT},
nullValueIterableMappingStrategy = NullValueMappingStrategy.{RETURN_NULL,RETURN_DEFAULT}
)
public interface MessageMapper {
MessageMapper INSTANCE = Mappers.getMapper(MessageMapper.class);
MessageBodyDto toMessageBodyDto(RequestDto requestDto);
}
@Mapper(unmmapedTargetPolicy = ReportingPolicy.{ERROR,WARN,IGNORE})
public interface MessageMapper {
MessageMapper INSTANCE = Mappers.getMapper(MessageMapper.class);
MessageBodyDto toMessageBodyDto(RequestDto requestDto);
}
public class BoardService {
public BoardResponseDto toDto() {
Board board = Board.builder()
.id(1)
.title("title")
.user(User.builder().username("username").build())
.userType(UserType.ADMIN).build();
return BoardMapper.INSTANCE.toBoardResponseDto(board);
}
}
public class RequestDto {
private String title;
private String content;
private String sender;
private List<String> receiver;
private LocalDateTime requestTime;
private String type;
}
public class MessageBodyDto {
private String title;
private String content;
private String sender;
private List<String> receiver;
private LocalDateTime requestTime;
private String type;
}
@Mapper
public interface MessageMapper {
MessageMapper INSTANCE = Mappers.getMapper(MessageMapper.class);
// RequestDto -> MessageBodyDto 매핑
MessageBodyDto toMessageBodyDto(RequestDto requestDto);
}
public class PageDto {
private Integer pageIndex;
private Integer pageCount
}
public class MessageServiceDto {
private String title;
private String content;
private String sender;
private List<String> receiver;
private String type;
private Integer pageIdx;
private Integer pageCnt;
}
public interface MessageMapper {
MessageMapper INSTANCE = Mappers.getMapper(MessageMapper.class);
//PageDto, RequestDto -> MessageServiceDto 매핑
@Mapping(source="pageDto.pageIndex", target="pageIdx")
@Mapping(source="pageDto.pageCount", target="pageCnt")
MessageServiceDto toMessageServiceDto(PageDto pageDto, RequestDto requestDto);
}
public class MessageListServiceDto {
private String messageId;
private Integer count;
private String title;
private String content;
private String sender;
private List<String> receiver;
private LocalDateTime requestTime;
}
public interface MessageMapper {
MessageMapper INSTANCE = Mappers.getMapper(MessageMapper.class);
//messageId, count, requestDto -> MessageServiceDto 매핑
MessageListServiceDto toMessageListServiceDto(String messageId, Integer count, RequestDto requestDto);
}
@Mapper(imports = UUID.class)
public interface MessageMapper {
MessageMapper INSTANCE = Mappers.getMapper(MessageMapper.class);
@Mapping(source = "messageId", target = "messageId", defaultExpression = "java(UUID.randomUUID().toString())")
@Mapping(source = "requestDto.type", target = "type", defaultValue = "SMS")
@Mapping(source = "requestDto.sender", target="sender", ignore=true)
MessageListServiceDto toMessageListServiceDto(String messageId, Integer count, RequestDto requestDto);
}
Timestamp createTimestamp
필드를 String createTimestamp
으로 변환할 때UserEntity userEntity
필드를 String username
으로 변환할 때@Mapping(source = "userEntity.username", target = "username")
으로 설정 가능@Mapper
public interface BoardProviderImplMapper {
BoardProviderImplMapper INSTANCE = Mappers.getMapper(BoardProviderImplMapper.class);
@Mapping(source = "createTimestamp", target = "createTimestamp")
default String toStringTime(Timestamp timestamp) {
return timestampToString(timestamp);
}
@Mapping(source = "userEntity", target = "username")
default String toUsername(UserEntity userEntity) {
return userEntity.getUsername();
}
@Mapping(source = "userEntity", target = "username")
BoardVo toBoardVo(BoardEntity boardEntity);
List<BoardVo> toBoardVoList(List<BoardEntity> boardEntityList);
}
.
으로 넘기고 default 메소드를 따로 만들어서 값을 리턴 @Mapper
public interface BoardMapper {
BoardMapper INSTANCE = Mappers.getMapper(BoardMapper.class);
@Mapping(source = "user", target = "username")
default String toUsername(User user) {
return user.getUsername();
}
@Mapping(target = ".", source = "likeCount")
default int toLikeCount(Board board) {
return 1;
}
@Mapping(source = "title", target = "boardTitle")
@Mapping(source = "user", target = "username")
@Mapping(source = "userType", target = "writerType")
@Mapping(source = ".", target = "likeCount")
BoardResponseDto toBoardResponseDto(Board board);
}
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "TB_BOARD")
public class Board extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Comment> comments = new LinkedHashSet<>();
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<BoardLike> likes = new LinkedHashSet<>();
@Builder
public Board(final String title, final String content, final User user) {
this.title = title;
this.content = content;
this.user = user;
}
}
public record BoardGetResponseDto(
String title,
String nickname,
String content,
LocalDateTime createdAt,
List<CommentResponseDto> comments
) {}
@Mapper
public interface BoardMapper {
BoardMapper INSTANCE = Mappers.getMapper(BoardMapper.class);
@Mapping(source = "user.nickname", target = "nickname")
CommentResponseDto toCommentResponseDto(Comment comment);
@Mapping(source = "user.nickname", target = "nickname")
BoardGetResponseDto toBoardResponseDto(Board board);
}