[Spring Boot] MapStruct
Source Code : https://github.com/junyharang-coding-study/JunyHarang-JAVA-EncryptionDecryption.git
"์ด ํฌ์คํ ์ ์ฟ ํก ํํธ๋์ค ํ๋์ ์ผํ์ผ๋ก, ์ด์ ๋ฐ๋ฅธ ์ผ์ ์ก์ ์์๋ฃ๋ฅผ ์ ๊ณต๋ฐ์ต๋๋ค."
๐ ๋ชฉ์ฐจ
โ [JAVA - Spring Boot] ์ํธํ/๋ณตํธํ ๊ธฐ๋ฅ ๋ง๋ค๊ธฐ
โ [Spring Boot] MapStruct
๐ MapStruct ๊ฐ์ง๊ณ ๋๊ธฐ
๐ฝ ๊ฐ์
๐ฆ ์ค๋ช
MapStruct๋ Entity ๊ฐ์ฒด์ DTO ๊ฐ์ฒด๋ฅผ ์ํธ Mapping ์ง์ํ๋ Library์์.
Entity์ DTO ๋ณํ์ ์ํด ๋ง์ ๋ฐฉ๋ฒ์ด ์์ง๋ง, ํด๋น ์์
์ ์ง์ํ๋ Library๋ ModelMapper์ MapStruct๊ฐ ๋ํ์ ์ด์์.
์ฃผ๋ํ๋์ ์ด๋ฒ์ MapStruct๋ฅผ ์ฌ์ฉํด๋ณด๋ คํ๋๋ฐ, ๊ทธ ์ด์ ๋ ModelMapper์ ๋น๊ตํ์ ๋, MapStruct์ ๊ฒฝ์ฐ Compile ์ ๋ฏธ๋ฆฌ ์์ฑ๋ ๊ตฌํ์ฒด๋ฅผ ํตํด Entity, DTO๋ฅผ ์ํธ Mapping ํด ์ฃผ๊ธฐ ๋๋ฌธ์ ํผํฌ๋จผ์ค ์ด์ ์ด ์๊ธฐ ๋๋ฌธ์ด์์.
๋ํ, ModelMapper์ ๊ฒฝ์ฐ ๋ณํ ๊ณผ์ ์์ ๋ฆฌํ๋ ์
์ด ๋ฐ์ํ๊ณ , MapStruct๋ ์์ ์ด์ ๋ก ๋ฆฌํ๋ ์
์ด ๋ฐ์ํ์ง ์๋ ์ด ์ ์ด ์๋ต๋๋ค.
๐ฝ ์ด๊ธฐ ๊ตฌ์ฑ
๐ฆ ์์กด์ฑ ์ค์
// MapStruct ์์กด์ฑ ์ถ๊ฐ
// https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor
// ์์ ์ฃผ์! Lombok ์ฌ์ฉ ์ Lombok ์์กด์ฑ ์ค์ ์ด ๋จผ์ ์์น
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
์์ ๊ฐ์ด MapStruct์ ์์กด์ฑ์ ์ถ๊ฐํด ์ฃผ์์ด์.
๐ฆ Interface ๊ตฌ์ฑ
MapStruct๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Generic์ ์ด์ฉํ์ฌ ๋จผ์ Interface๋ฅผ ๋ง๋ค์ด์ค์ผ ํด์.
์์ ๊ฐ์ด GenericMapper๋ฅผ ๋ง๋ค์ด ์ฃผ์์ด์.
์ Interface์์ toDTO์ toEntity๋ ๊ฐ๊ฐ DTO๊ฐ ๋ค์ด์ค๋ฉด Entity๋ก ๋ณํํด์ฃผ๊ณ ,
Entity๊ฐ ๋ค์ด์ค๋ฉด DTO๋ก ๋ณํํด์ฃผ๋ ์ถ์ Method์์.
๊ทธ ์๋ List Method๋ค์ List๋ก ์์
์ ํ ๋ ์ฐ๊ธฐ ์ํด ์ ์ธํด ์ฃผ์์ด์.
16๋ฒ์งธ Annotaion๊ณผ ๊ฐ์ด ์ค์ ์ ๋ฃ๊ฒ ๋๋ฉด nullValuePropertyMappingStrategy๋ก ์ธํด
Null ๊ฐ์ด ์ ๋ฌ๋ ๊ฒฝ์ฐ ๋ณํ ์์ผ์ฃผ์ง ์๋๋ก ์ค์ ์ ํ์ฌ ์ฃผ์์ด์.
updateFromDTO()๋ ๊ธฐ์กด ์์ฑ๋์ด ์๋ Entity๋ฅผ Updateํ๊ณ ์ถ์ ๋,
Null์ด ์๋ ๊ฐ๋ง Updateํ ์ ์๋๋ก ๋ง๋ค์ด์ค ์ถ์ Method์์.
๐ก ์ฐธ๊ณ ์ฌํญ
@MappingTarget : ๋ณํํ์ฌ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ๊ฒ์ด ์๋ ๋งค๊ฐ ๋ณ์(์ธ์)๋ก ๋ฐ์ Updateํ Target ์ค์ .
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) : Source Field๊ฐ Null์ด๋ผ๋ฉด Null ๊ฐ ๋ฌด์.
๐ฝ ์ฝ๋ ๊ตฌํ
๐ฆ BoardMapper
์ค์ ๋ก MapStruct๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๋์ ๊ฐ์ด ์ฝ๋๋ฅผ ๋ง๋ค์ด์ค์ผํด์.
์์ ๊ฐ์ด GenericMapper๋ฅผ ์์๋ฐ๋๋ฐ, GenericMapper์ Generic Type์ผ๋ก Mapping ํ๊ณ ์ํ๋ DTO์ Entity๋ฅผ ๋ช
์ํด ์ฃผ๋ฉด ๋์.
๊ทธ๋ฆฌ๊ณ , @Mapper๋ฅผ ํตํด mapstruct๊ฐ ์ธ์๋๋๋ก ํ์ฌ์ฃผ์์ด์.
๐ก ์ฐธ๊ณ ์ฌํญ
@Mapper : MapStruct Code Generator๊ฐ ํด๋น Interface์ ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค์ด ์ค.
componentModel = "spring" : Spring ํ๊ฒฝ์ ๋ง๊ฒ Bean ๋ฑ๋ก
์ด์ ์ด ์ํ์์ Spring Boot๋ฅผ ๊ธฐ๋ํ๊ฒ ๋๋ฉด MapperImpl์ด ์๋์ผ๋ก ๋ง๋ค์ด์ง๊ฒ ๋์.
package com.junyharang.endecrypttest.common.mapper;
import com.junyharang.endecrypttest.model.dto.request.BoardRequestDTO;
import com.junyharang.endecrypttest.model.dto.request.BoardRequestDTO.BoardRequestDTOBuilder;
import com.junyharang.endecrypttest.model.entity.TestBoard;
import com.junyharang.endecrypttest.model.entity.TestBoard.TestBoardBuilder;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.processing.Generated;
import org.springframework.stereotype.Component;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-12-07T22:50:19+0900",
comments = "version: 1.4.2.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-7.5.1.jar, environment: Java 11.0.16 (Azul Systems, Inc.)"
)
@Component
public class TestBoardMapperImpl implements TestBoardMapper {
@Override
public BoardRequestDTO toDTO(TestBoard entity) {
if ( entity == null ) {
return null;
}
BoardRequestDTOBuilder boardRequestDTO = BoardRequestDTO.builder();
boardRequestDTO.title( entity.getTitle() );
boardRequestDTO.content( entity.getContent() );
return boardRequestDTO.build();
}
@Override
public TestBoard toEntity(BoardRequestDTO dto) {
if ( dto == null ) {
return null;
}
TestBoardBuilder testBoard = TestBoard.builder();
testBoard.title( dto.getTitle() );
testBoard.content( dto.getContent() );
return testBoard.build();
}
@Override
public List<BoardRequestDTO> toDTOList(List<TestBoard> entityList) {
if ( entityList == null ) {
return null;
}
List<BoardRequestDTO> list = new ArrayList<BoardRequestDTO>( entityList.size() );
for ( TestBoard testBoard : entityList ) {
list.add( toDTO( testBoard ) );
}
return list;
}
@Override
public List<TestBoard> toEntityList(List<BoardRequestDTO> dtoList) {
if ( dtoList == null ) {
return null;
}
List<TestBoard> list = new ArrayList<TestBoard>( dtoList.size() );
for ( BoardRequestDTO boardRequestDTO : dtoList ) {
list.add( toEntity( boardRequestDTO ) );
}
return list;
}
@Override
public void updateFromDTO(BoardRequestDTO dto, TestBoard entity) {
if ( dto == null ) {
return;
}
}
}
์์ ๊ฐ์ด ์๋์ผ๋ก ๊ตฌํ์ฒด Class๊ฐ ์๊ธฐ๋ ๊ฒ์ ํ์ธํ ์ ์์ด์.
๐ฝ ์ฝ๋ ์ฌ์ฉ
๐ฆ Service Layer
์๋๋ ์์ ๊ฐ์ด Entity์ Builder Pattern์ ํตํด DTO๋ฅผ Entity๋ก ๋ฐ๊พธ๋ Logic์ด์๋ ๊ฒ์
MapStruct๋ฅผ ์ฌ์ฉํด์ ๋ณ๊ฒฝํด ๋ณผ๊ฒ์.
์์ ๊ฐ์ด ์์ ์ ์๋ฃ ํด ์ฃผ์์ด์.
์ต์ด content ๊ฐ์ ์ํธํ ํด์ฃผ๋ Logic์ด ๊ตฌํ๋์ด ์์๊ธฐ ๋๋ฌธ์ RequestDTO์ setContent()๋ฅผ ํธ์ถํ์ฌ ์ํธํ Logic์ ํ ๋ค ๋ฐํ๋๋ ์ํธ๊ฐ์ด content Field์ ์ ์ฅ๋๋๋ก ํ์ฌ์ฃผ์์ด์.
๊ทธ๋ฐ ๋ค 43๋ฒ์งธ ์ค์ BoardReqeustDTO ๊ฐ์ฒด๋ฅผ Entity๋ก ๋ณํํด์ฃผ๋ Method๋ฅผ ํตํด Entity๋ฅผ ์ป๊ฒํ๊ณ , ๊ทธ๊ฒ์ Repository๋ก ๋ณด๋ด๊ฒ ๊ตฌํํด ์ฃผ์์ด์.
์์ ๊ฐ์ด ๊ธ ์์ฑ์ ์ฑ๊ณตํ์์ด์.
์์ธ ์กฐํ ๊ธฐ๋ฅ์ ์ด์ฉํ์ ๋๋ ์ ์์ ์ผ๋ก ๋ณตํธํ๊ฐ ๋๋ฉด ๊ฐ์ ๋ฐํ๋ฐ์ ๊ฒ์ ํ์ธํ ์ ์์ด์.
๐ง ์ฐธ๊ณ ์๋ฃ
"์ด ํฌ์คํ ์ ์ฟ ํก ํํธ๋์ค ํ๋์ ์ผํ์ผ๋ก, ์ด์ ๋ฐ๋ฅธ ์ผ์ ์ก์ ์์๋ฃ๋ฅผ ์ ๊ณต๋ฐ์ต๋๋ค."