들어가기에 앞서..
Study를 하며 Controller, Service, Repsitory와 같은 여러 계층에서 데이터를 서로 교환할 때 사용하는 DTO와 Entity를 서로 형변환 해주는 과정을 하는데 이를 효율적으로 사용해주기 위해서 MapStruct를 사용하기로 했다.
EntityToDto, DtoToEntity 와 같은 메서드를 생성해주고 이를 모든 entity마다 관리를 해주게 되면, 단순 반복적인 코드이기에 실수도 나올 수 있고, 코드도 지저분하며 또한 비용측면에서 비효율적이다. 이를 해결해주기 위해서 여러 Mapping 라이브러리가 존재하지만, 이번에는 MapStruct를 공부하고 사용해 볼 것이다.
해당 기능에 대해 들어본적 있지만 사용해본적과 공부해본적이 없기에 정리해보려고 한다.
MapStruct란?
MapStruct는 자바에서 객체 간 매핑에 대한 코드를 자동으로 생성해주는 매핑 라이브러리이다. 어노테이션을 사용하여 컴파일 시 매핑 코드를 생성한다. 즉, MapStruct는 Java Bean 유형 간의 매핑 구현을 단순화하는 코드 생성기를 말한다.
MapStruct를 사용하는 이유는 무엇인가?
MapStruct의 라이브러리 홈페이지를 참조해서 해석해보면, 앞서 위에서 내가 설명했던 이유와 비슷하다. 종종 일어나는 객체들에 대한 매핑 코드를 작성하는 것은 단순 지루하고, 오류가 발생할 수 있고, 비용적 측면에서 비효율 적이므로 이를 자동화하여 단순화 하기 위한 것이다.
추가적으로, 만약 Builder나 Setter를 사용하여 매핑코드를 작성해주어도 되지만, 만약 필드 추가나 수정이 된다고 생각해보자. 그렇다면 해당 객체를 사용하는 곳에서 많은 코드를 수정해줄 가능성도 존재하게 된다. 하지만, MapStruct를 사용하게 된다면 어노테이션을 통해 추가 및 수정을 해주면 된다고 한다.
MapStruct의 특징
- 컴파일 시점에 코드를 생성하여 런타임에서 안정성을 보장
- 다른 매핑 라이브러리보다 속도 측면에서 효율적
- 반복되는 객체 매핑에서 발생할 수 있는 오류를 줄이게 되고, 구현 코드를 자동으로 생성해주기에 손쉽다.
- Annotation processor를 이용하여 객체간 매핑을 자동으로 제공
- dependency 추가가 필요
- 생성된 매핑 코드를 눈으로 확인이 가능
- 디버깅이 쉽다
MapStruct 적용 예제
우선 MapStruct를 사용해주기 위해서는 maven 이나 gradle에 dependency를 추가해줘야 한다. 하지만, Lombok을 사용할시 주의점이 존재한다. Lombok과 MapStruct를 둘다 사용할 시 의존성을 추가해줄 때, 추가해준 순서에 따라 다르게 작동하기에 Lombok 다음에 MapStruct를 선언해줘야 한다.
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
public class Owners {
private String name;
private String address;
private String city;
private String telephone;
}
public class OwnersDto {
private String nickname;
private String address;
private String city;
private String telephone;
}
예를 들어 위와 같은 Entity와 Dto가 있다고 가정해보자.
@Mapper(componentModel = "spring")
public interface OwnersMapper {
OwnersMapper INSTANCE = Mappers.getMapper(OwnersMapper.class);
@Mapping(source = "nickname", targer = "name")
Owners dtoToEntity(OwnersDto dto);
@Mapping(source = "name", target = "nickname")
OwnersDto entityToDto(Owners entity);
}
- componentModel = "spring"
: Spring에서 사용 시, Impl은 스프링의 싱글톤 빈으로 관리 - @Mapping(source = "A", target = "B")
: 변환될 클래스와 변환 대상 클래스의 필드 이름이 다르다면, 위와 같이 직접 지정이 가능하다.
(source가 변환시킬 곳에 있는 것, target이 변환될 곳에 있는 것) - @Mapping(source = "A", ignore = true)
: 매핑 시 타겟 객체에 매핑되지 않는 필드가 있다면 위와 같이 ignore을 사용하여 무시해줄 수 있다.
Owners owners = OwnersMapper.INSTANCE.dtoToEntity(OwnersDto);
OwnersDto ownersDto = OwnersMapper.INSTANCE.entityToDto(Owners);