[Spring] MapStruct 적용기
이전에 객채 생성을 위해 Builder 패턴을 적용해봤다. 근데 프로젝트를 하다보면 엔티티 클래스 하나만 계속 참조하지 않고 필요에
따라 DTO를 정의해서 필요한 내용들만 담을 수 있도록 활용하며 때로는 DTO에서 DTO로 데이터를 넘겨줘야 하는 경우도 생긴다.
이 때 편리하게 객체를 생성할 수 있도록 도와주는게 MapStruct다!
MapStruct 적용하기
설정
build.gradle
에서 다음 의존성을 추가해주자.
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
Mapper 클래스 생성
RequestLoginDto
정보를 기반으로 LoginDto
를 생성할 수 있도록 해보자.
각 클래스 정보는 다음과 같다.
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RequestLoginDto {
private String email;
private String password;
}
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LoginDto {
private String email;
private String password;
}
- 변환 전 객체(
RequestLoginDto
)는 값을 가져올 수 있도록@Getter
를 설정해줘야 함 - 변환 후 객체(
LoginDto
)는 객체를 생성할 수 있도록 다음 중 1개라도 정의가 돼있어야 함@Builder
@AllArgsConstructor
@Setter
@NoArgsConstructor
와@AllArgsConstructor
를 같이 사용할 땐@NoArgsConstructor(access = AccessLevel.PROTECTED)
를 사용해야 함
Mapper 클래스를 UserMapper
라 하면, 다음처럼 작성해주면 된다.
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
LoginDto toLoginDto(RequestLoginDto requestLoginDto);
}
- 함수명(
toLoginDto
)은 상관없음
RequestLoginDto
와 LoginDto
는 서로 갖고있는 필드가 완전히 같아 위처럼 쉽게 설정해줄 수 있다.
빌드를 하면 build/classes/
에서 UserMapper
에 적어준 내용 기반으로 어떻게 매핑하는지 구현체 UserMapperImpl
이
생성된다. 아래는 UserMapperImpl
내용이다.
public class UserMapperImpl implements UserMapper {
public UserMapperImpl() {
}
public LoginDto toLoginDto(RequestLoginDto requestLoginDto) {
if (requestLoginDto == null) {
return null;
} else {
LoginDto.LoginDtoBuilder loginDto = LoginDto.builder();
loginDto.email(requestLoginDto.getEmail());
loginDto.password(requestLoginDto.getPassword());
return loginDto.build();
}
}
}
- 내부적으로 Builder 패턴을 통해 객체를 생성해주고 있음
그런데 변환하려는 클래스의 필드에 값을 저장해야 하는데 참조할 필드가 없거나, 필드명이 다르거나, 참조하지 않는 등 여러 제약 조건
을 걸어야 할 때도 있다. 이 경우 @Mapping
을 사용할 수 있으며 여러 @Mapping
을 사용하는 경우 @Mappings
로 감싸서
한번에 적용한다.
아래는 필드명이 다를 때 어떻게 설정하는지 그리고 어떻게 변환되는지에 대한 내용이다.
//변환할 클래스 정보
public class ResponseImage {
private Long id;
private String url;
}
//Mapper 설정
@Mapper
public interface ImageMapper {
ImageMapper INSTANCE = Mappers.getMapper(ImageMapper.class);
@Mapping(source = "imageUrl", target = "url") //imageUrl -> ResponseImage.url
ResponseImage toResponseImage(String imageUrl);
}
//빌드 후 결과
public class ImageMapperImpl implements ImageMapper {
public ImageMapperImpl() {
}
public ResponseImage toResponseImage(String imageUrl) {
if (imageUrl == null) {
return null;
} else {
ResponseImage.ResponseImageBuilder responseImage1 = ResponseImage.builder();
responseImage1.url(imageUrl);
return responseImage1.build();
}
}
}
만약 참조하고 싶지 않다면 ignore = true
를 설정하면 되고, source
에 빈 값이 들어오는 경우에는 설정해준 기본값을 입력
해줄 수 있다. 이 때는 defaultValue
또는 defaultExpression
을 사용한다.
public class RequestImage {
private Long id;
private String url;
}
@Mapper
public interface ImageMapper {
ImageMapper INSTANCE = Mappers.getMapper(ImageMapper.class);
@Mappings({
@Mapping(source = "requestImage.id", target = "id", ignore = true), //requestImage.id 매핑 안함
@Mapping(source = "replacedUrl", target = "url", defaultValue = "defaultUrl") //기본값 replacedUrl
})
ResponseImage toResponseImage(RequestImage requestImage, String replacedUrl);
}
활용
바로 위에 toResponseImage()
를 사용해서 ResponseImage
를 만들고 싶다면 다음처럼 사용하면 된다.
RequestImage request = RequestImage.builder()
.id(1L)
.url("request")
.build();
String replacedUrl = "replace";
//toResponseImage()로 ResponseImage 생성
ResponseImage response = ImageMapper.INSTANCE.toResponseImage(request, replacedUrl);
댓글남기기