본문 바로가기
Web/Spring

[Mapper] MapStruct로 Dto/Entity 매핑하기(vs ModelMapper)

by 정고정 2021. 11. 7.
반응형

스터디원 중에 한 분이 엔티티매퍼 알려주셔서 와 신세계다 이러고 쓰고 있었는데 예전 프로젝트들 까보니까 익숙하게 ModelMapper 쓰고 있었음...... 스프링 너무 간만인데다 어디 기록도 안 해두니까 다 까먹는다.

 

그런데 기존에 사용하던 ModelMapper는 modelMapper.map(ENTITY, DTO.Class) 형태로 사용할 때 리플렉션이 일어나서 MapStruct보다 성능이 떨어진다고 한다. 그래서 이번 프로젝트부터는 MapStruct를 사용하기로 했다. 알려주셔서 감사합니다.

 

mapstruct는 프로젝트를 빌드하면 mapstruct의 @Mapper가 달린 interface의 구현클래스를 자동으로 생성해 준다.

 

pom.xml

<properties>
	<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
    <org.projectlombok.lombok-mapstruct-binding.version>0.2.0</org.projectlombok.lombok-mapstruct-binding.version>
</properties>

<dependencies>
	<!-- mapstruct -->
	<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct</artifactId>
		<version>${org.mapstruct.version}</version>
	</dependency>
</dependencies>

<!--lombok과 같이 사용할 경우-->
<build>	
	<defaultGoal>install</defaultGoal>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<configuration>
				<annotationProcessorPaths>
             	   <path>
						<groupId>org.projectlombok</groupId>
						<artifactId>lombok</artifactId>
						<version>${org.projectlombok.version}</version>
					</path>
					<path>
						<groupId>org.mapstruct</groupId>
						<artifactId>mapstruct-processor</artifactId>							<version>${org.mapstruct.version}</version>
					</path>
                    <path>
						<groupId>org.projectlombok</groupId>
						<artifactId>lombok-mapstruct-binding</artifactId>
						<version>${org.projectlombok.ombok-mapstruct-binding.version}</version>
					</path>
				</annotationProcessorPaths>
			</configuration>
		</plugin>
	</plugins>
</build>

properties로 mapstruct 버전 잡아주고, dependency 추가해주고, lombok과 같이 사용하기 위해 build plugin을 저 순서로 설정해줬다.

 

버전 업그레이드 되면서 이제는 둘이 호환이 잘 된다고 하는데 그래도 mapstruct와 lombok은 쉽게 충돌할 수 있는 친구들이니까 따로 신경 써 줘야 한다.  롬복 @Builder를 사용할 거라 lombok-mapstruct-bind도 넣어준다.

bind dependency를 넣어주지 않으면 mapstruct가 냅다 build만 가져다 써버려서 Impl클래스에 setter가 하나도 안 붙는다. 

-> 빈 객체를 리턴하게 됨 

 

mapstruct는 프로젝트를 빌드하면 mapstruct의 @Mapper가 달린 interface의 구현클래스를 자동으로 생성해 준다.

maven에서 빌드를 진행하기 위해 defaultGoal도 설정해 줬다.

 

이클립스 STS의 경우 클래스 생성위치를 메이븐이 못 잡는다. 때문에 위의 pom.xml에서 build>configuration 안에 따로 패스를 작성해줘야 한다. gradle의 경우 sourceSets로 설정해 주면 된다. 

<sources>
	<source>${project.build.directory}/generated-sources/annotations</source>
</sources>

 

Dto/Entity 준비

Dto를 Entity로 바꿔주는 함수를 작성할 건데 내 경우는 각각 대응되는 필드명이 같다.

 

MemberDtoMapper.java

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper
public interface MemberDtoMapper{
	MemberDtoMapper INSTANCE = Mappers.getMapper(MemberDtoMapper.class);
	Member toEntity(MemberSaveRequestDto requestDto);
}

이미 마이바티스 매퍼를 사용하고 있어서 bean이름이 겹치는 걸 방지하려고 MemberDtoMapper로 네이밍했다. 

(MemberRepository는 JpaRepository가 이미 쓰고 있음...)

다음과 같이 본인클래스를 인스턴스로 설정해주고, Dto를 파라미터로 받아 Entity를 리턴하는 toEntity()를 선언해준다.

만일 파라미터와 반환타입에서 대응되는 필드 이름이 다르다면 메소드 위에 @Mapping(source = "dtoname", target = "entityname") 처럼 어노테이션을 달아주면 된다. (toEntity의 경우)

 

Generic Interface를 선언해서 각각 상속받게 하면 조금 더 편하게 쓸 수 있기는 한데 일단은 환경만 구성해 놓는 거라 일단 그냥 작성해줬다. 

 

MemberDtoMapperImpl.java

Maven update-Maven Clean-Maven Build

빌드하면 이클립스 STS 기준 target/generated-sources/${본인패키지구조}/에 Impl클래스가 생긴다. 인텔리제이는 메이븐에서 인식해주는 경로로 잘 잡힌다.

 

MemberDtoMapperTest.java

@Slf4j
public class MemberDtoMapperTest {

	@Test
	public void toEntity() {
		MemberSaveRequestDto requestDto = MemberSaveRequestDto.builder()
				.name("test").tel("test").address("test").email("test").password("test")
				.build();
		Member member = MemberDtoMapper.INSTANCE.toEntity(requestDto);
		assertEquals(member.getEmail(), "test");
	}
}

toEntity()가 리턴한 Member객체에 Dto값이 제대로 들어가 있다. 

 

 

 

만약 Impl이 만들어졌는데도 못 찾는다는 에러가 뜨면 pom.xml에 있는 source 경로 다시 확인해보고 업데이트클린빌드새로고침 반복하기......

잘 되던 게 어느날 갑자기 에러 뜨면 업데이트클린빌드새로고침하기......

이클립스는 메이븐이랑 SourceSet이 다른 거 같다던데 진짜 규정만 아니었어도 갈아탔다

 

+)

List 등의 Collection을 매핑해야 할 경우 제네릭으로 지정된 dto 자체에 대한 매핑부터 명시를 해줘야 한다. 

빌드된 Mapper코드를 까 보면 Collection 타입을 매핑할 때는 foreach문 돌면서 target dto를 생성해버림

LIst<AuthDto>를 다른 dto로 매핑하고 싶을 경우 List<AuthDto>와 AuthDto에 대한 함수원형을 모두 작성해줘야 제대로 된 mapper가 생성된다.  

 

 

반응형

댓글