[Spring] MapStruct
1. MapStruct 란?
MapStruct 는 매핑된 object 를 서로 변환해주는 기능을 제공하는 라이브러리이다. mapper 인터페이스에서 두개의 Java Bean 타입에 대해서 매핑하는 함수를 선언하면 MapStruct 는 해당 인터페이스를 자동으로 구현하여 매핑 함수를 생성해준다.
스프링을 사용하다 보면 객체를 다른 객체로 변환해야 하는 경우가 있다. 주로 DTO 와 DB Entity 간의 변환이 많이 발생한다. 이런 부분에서 MapStruct 를 사용한다면 bean mapper 함수를 따로 구현하지 않고 자동으로 변환할 수 있도록 할 수 있다.
MapStruct 는 어노테이션 기반으로 동작한다. @Mapper 어노테이션을 통해서 mapper 인터페이스를 선언하고 메서드를 선언하여 사용할 수 있다. 그외에도 @Mapping 어노테이션을 사용하여 객체의 필드 간에 매핑을 정의할 수 있다.
2. MapStruct 예제
- gradle 설정
스프링에서 MapStruct 를 사용하기 위해서는 MapStruct 라이브러이에 대한 dependency 설정을 추가해주어야 한다. gradle 을 사용하는 경우에는 아래와 같이 build.gradle 의 dependencies 에 mapstruct 라이브러리를 추가해주면 된다.
dependencies {
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
}
// https://mapstruct.org/documentation/installation/
- Java Bean 구현
MapStruct 를 통해 매핑할 자바 객체를 구현한다.
@Getter
@Setter
public class Source {
private String id;
private String name;
private String source;
}
@Getter
@Setter
public class Dest {
private String id;
private String name;
private String dest;
}
Source 와 Dest 는 각각 id 와 name 이라는 필드를 가지는 간단한 클래스이다. MapStruct 를 사용하기 위해서는 변환 되기 전의 객체는 필드 값을 가져올 수 있도록 getter 가 있어야 하고 변환 결과의 객체는 필드 값을 설정할 수 있는 setter 가 필요하다. 예제에서는 lombok 을 사용하여 각각의 클래스에 getter, setter 를 추가해주었다.
- Mapper Interface
Mapper 인터페이스를 구현한다. @Mapper 어노테이션으로 지정해준 인터페이스 내부에 Source to Dest 와 Dest to Source 라는 각각의 클래스 간의 변환을 위한 메서드를 선언해준다. 이때 @Mapping 어노테이션을 사용하면 서로 다른 이름을 가진 필드를 매핑해줄 수 있다. 예제에서는 Source 의 source 필드와 Dest 의 dest 필드를 매핑하였다.
@Mapper
public interface CustomMapper {
@Mapping(target = "dest", source = "source.source")
public Dest sourceToDest(Source source);
@Mapping(target = "source", source = "dest.dest")
public Source destToSource(Dest dest);
}
MapStruct 는 컴파일 시점에 Mapper Interface 를 구현한 클래스를 자동 생성한다. 그렇기 때문에 해당 프로젝트를 빌드한 후에 프로젝트의 'bin/generated-sources/annotations/' 아래를 찾아보면 MapStruct 가 구현한 CustomMapperImpl.java 라는 코드를 발견할 수 있다.
@Mapper(componentModel = "spring")
public interface CustomMapper { ... }
스프링에서는 Mapper 클래스를 스프링 빈으로 등록할 수 있다. Mapper 어노테이션의 componentModel 을 "spring" 으로 정의해주면 자동으로 스프링 빈에 등록되어 @Autowired 를 통해 사용할 수 있다. 만약 따로 componentModel 을 지정하지 않으면 default 로 값이 들어가는데, 이 경우에는 Mapper.getMapper(Class) 메서드를 통해서 mapper 객체를 사용할 수 있다.
- Test code
@SpringBootTest
public class CustomMapperTest {
@Autowired
CustomMapper mapper;
@Test
public void testCustomMapper() {
Source source = new Source();
source.setId("testId");
source.setName("testName");
Dest dest = mapper.sourceToDest(source);
assertEquals(source.getId(), dest.getId());
assertEquals(source.getName(), dest.getName());
source = mapper.destToSource(dest);
assertEquals(source.getId(), dest.getId());
assertEquals(source.getName(), dest.getName());
}
}
앞에서 구현한 Java Bean 들과 이들을 변환하는 mapper 인터페이스를 사용하여 실제 객체를 변환하는 테스트 코드를 작성하였다.
mapper 는 @Autowired 로 injection 하도록 하였다. 테스트 코드는 Source 객체를 Dest 로 변환한 다음 id, name 을 비교하고, Dest 객체를 다시 Source 객체로 변환하여 id, name 을 비교하도록 구현하였다. 이 테스트 코드로 각각의 변환이 정상적으로 이루어지는지 확인할 수 있다.
[Reference]
- https://www.baeldung.com/mapstruct
- https://mapstruct.org/documentation/installation/