Map Struct
๐ก MapStruct๋ ๊ตฌ์ฑ ์ ๊ทผ๋ฒ์ ๋ํ ๊ท์ฝ์ ๊ทผ๊ฑฐํ์ฌ Java Bean ์ข ๋ฅ ๊ฐ์ ๋งคํ ๊ตฌํ์ ํฌ๊ฒ ๋จ์ํํ code generator ์ด๋ค.
๊ฐ๋จํ๊ฒ ๋งํ๋ฉด Dto ์ Entity์ ๋ณํ์ ์ฝ๊ฒ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด Map Struct๋ฅผ ์ ์ฌ์ฉํ ๊น?
์ฅ์
- ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ์ปดํ์ผ์ ๋งคํ์ฝ๋๊ฐ ์์ฑ๋๊ธฐ๋๋ฌธ์ ์ปดํ์ผ ์ค๋ฅ ํ์ธ์ด ๊ฐ๋ฅํ๋ค. (์ปดํ์ผ ์์ ์ ๋งคํ ์ ๋ณด๊ฐ ํ์ ์ธ์ดํํ ์ง๋ฅผ ๊ฒ์ฆ)
- ๋ฆฌํ๋ ์ ์ด ์๋ ์ง์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ๋์ํ์ฌ ์๋๊ฐ ๋น ๋ฅด๋ค.
- ๋๋ฒ๊น ์ด ์ฝ๋ค.
- ๊ตฌํ์ฒด๋ฅผ ์๋์ผ๋ก ์์ฑ ๋๊ธฐ๋๋ฌธ์ ์์ฑ๋ ๋งคํ ์ฝ๋๋ฅผ ๋์ผ๋ก ํ์ธ ํ ์ ์๋ค.
→ ์ค์ ๊ฑฐ๋ํ ์์คํ ์ ๋ง๋ค๋ค๋ณด๋ฉด, ๋ค์ํ ๋๋ฉ์ธ ์์ญ๋ค์ด ์๊ณ ์ด๋ฅผ ๋ณํํ๋ ์์ ์ ๊ฐ๋ฐ์์๊ฒ ์์ด์ ๋งค์ฐ ๋ฒ๊ฑฐ๋กญ๊ณ ์ค์ํ๊ธฐ ์ข๋ค. mapstruct ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ์ค์๋ฅผ ์ค์ฌ ์์ ํ ๊ฐ๋ฐ์ ํ ์ ์์ ๊ฒ๊ฐ๋ค.
์ฌ์ฉํด๋ณด๊ธฐ
์์กด์ฑ ์ถ๊ฐ
dependencies {
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
// lombok ์์ mapstruct binding ํด์ฃผ๋ ๊ธฐ๋ฅ
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
}
lombok๊ณผ mapstruct ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํด์ค๋ค.
์ด๋, lombok-mapstruct-binding ์์กด์ฑ์ ์ถ๊ฐํด์ฃผ์ง ์์ผ๋ฉด lombok๊ณผ mapstruct ์์กด์ฑ ์ ์ธ ์์์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์ํ ์ ์๋ค.
์ฌ์ฉํ๊ธฐ
UserEntity
@AllArgsConstructor
@Getter
@Builder
public class User {
private Long id;
private String name;
private Integer age;
private LocalDateTime createdTime;
}
UserDto
@AllArgsConstructor
@Getter
@Builder
public class UserDto {
private String name;
private Integer age;
private String email;
private LocalDateTime created;
}
Entity ๊ฐ์ฒด์ Dto ๊ฐ์ฒด๊ฐ ๊ฐ์ง ํ๋๊ฐ ์กฐ๊ธ์ฉ ๋ค๋ฅด๋ค.
- UserDto์๋ id ํ๋๊ฐ ์กด์ฌํ์ง ์๊ณ User์๋ email ํ๋๊ฐ ์กด์ฌํ์ง ์๋๋ค. (ํ๋ ๋ถ์ผ์น)
- ์์ฑ์๊ฐ์ด UserDto๋ created ํ๋๋ก User์๋ createdTime๋ก ์ ์ธ๋์ด ์๋ค. (ํ๋ ์ด๋ฆ ๋ถ์ผ์น)
โ๏ธ ๊บผ๋ด์ค๋ ๊ฐ์ฒด(source)์๋ Getter๊ฐ ์์ด์ผํ๊ณ ์ ์ฅํ๋ ๊ฐ์ฒด(target)์๋ Builder ํน์ AllArgsConstructor๊ฐ ์์ด์ผํ๋ค.
Mapper๋ฅผ ๋ง๋ ๋ค.
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper // 1
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); // 2
// 3
@Mapping(target = "id", constant = "0L")
@Mapping(source = "created",target = "createdTime")
User userDtoToEntity(UserDto userDto);
// 4
@Mapping(target = "email", expression = "java(user.getName() + \"@naver.com\")")
@Mapping(source = "createdTime",target = "created")
UserDto userToDto(User user);
}
- ๋งคํ ์ธํฐํ์ด์ค์์ ๋ช ์ํ๋ค.
- ์ฌ์ฉ์๊ฐ ์ ๊ทผํ ์ ์๋ ๋งคํผ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ์ค๋ค. (์ฌ๊ธฐ์ Instance๊ฐ UserMapper๋ฅผ ์์๋ฐ์์ UserMapperImpl๋ฅผ ๊ตฌํํด์ค๋ค. )
- UserDto → User ๋ฉ์๋
- User์๋ง ์๋ id ํ๋๋ฅผ ์์ "0L"๋ก ์ง์ ํ๋ค.
- ( id ์์ฑ ์ ๋ต์ด Auto Increment์ฌ์ ๊ฐ์ ๋์ ํ๊ณ ์ถ์ง ์์๋๋ @Mapping(target = "id", ignore = true) ์ฌ์ฉํ ์ ์๋ค. )
- UserDto์ created ํ๋๋ฅผ User์ createdTime ํ๋๋ก ์ ์ฅ
- UserDto์๋ง ์๋ email ํ๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฌด์๋๋ค.
- User → UserDto ๋ฉ์๋
- UserDto์ email ํ๋๋ฅผ expression์ ๊ธฐ๋ฐ์ผ๋ก name@naver.com์ผ๋ก ๋ง๋ ๋ค.
์์ฉ
- ์ฌ๋ฌ ํ๋ผ๋ฏธํฐ๋ฅผ ๋งคํํ๋ค.
@Mapper
public interface AddressMapper {
@Mapping(source = "person.description", target = "description")
@Mapping(source = "address.houseNo", target = "houseNumber")
AddressDto personAndAddressToAddressDto(Person person, Address address);
}
๋๊ฐ์ง ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐํฉํ์ฌ Dto๋ก ๋งคํ
- ์กฐ๊ฑด์ด ์ฌ๋ฌ๊ฐ๋ผ๋ฉด @Mappings๋ก ๋ฌถ์ด์ ์ธํ ํด์ค ์ ์๋ค.
@Mapper
public interface UserMapper {
@Mappings({
@Mapping(source = "nickname2", target = "nickname"),
@Mapping(target = "address", ignore = true),
@Mapping(target = "name", ignore = true),
@Mapping(target = "age", ignore = true)
})
UserSaveRequestDto from(UserSaveRequest2 userSaveRequest);
}
- mapper ์ด๋ ธํ ์ด์ ์ ์์ฑ์ ์ถ๊ฐํ ์ ์๋ค.
@Mapper(
componentModel = "spring", // ๋น๋ ์ ๊ตฌํ์ฒด ๋ง๋ค๊ณ ๋น์ผ๋ก ๋ฑ๋ก
injectionStrategy = InjectionStrategy.CONSTRUCTOR, // ์์ฑ์ ์ฃผ์
์ ๋ต
unmappedTargetPolicy = ReportingPolicy.ERROR // ์ผ์นํ์ง ์๋ ํ๋๊ฐ ์์ผ๋ฉด ๋น๋ ์ ์๋ฌ
)
public interface UserMapper {
...
}
unmappedTargetPolicy = ReportingPolicy.IGNORE ๋ก ์ค์ ํ๋ฉด ๊ฐ์ ๋์ ํ๊ณ ์ถ์ง ์์ ํ๋๋ฅผ ์ผ๊ด ignore ์ฒ๋ฆฌํ ์์๋ค.
- ์ค์ฒฉ(nested) ๋งคํ์ ํ ์ ์๋ค.
@Mapper
public interface OrderMapper {
@Mapping(source = "OrderItemDto", target = "orderItem")
@Mapping(source = "OrderItemDto.amount", target = "orderItem.count")
Order OrderCreateDtoToOrder(OrderCreateDto orderCreateDto);
}
OrderCreateDto์ orderItemDto ํ๋ ๊ฐ์ฒด๋ฅผ Order ์ํฐํฐ orderItem ํ๋ ๊ฐ์ฒด์ ๋งคํํ๋ค. ์ด๋ orderItemDto์ amount๊ฐ์ orderItem์ count์ ๋งคํํ๋ค.
MapStruct๊ฐ ์์ฑํ ๊ตฌํ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๋๊ฐ์ง ๋ฐฉ์
1. Mapper Factory ์ฌ์ฉ
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
....
}
์์ฑ๋ Mapper ์ธ์คํด์ค๋ฅผ ๋ฐ์์ ์ฌ์ฉํ ์ ์๋ค.
์ด ํจํด์ mapper๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ์ธ์คํด์ค๋ฅผ ์๋ก ์์ฑํ ํ์์์ด ์ฑ๊ธํค์ผ๋ก ์์ฑ๋ ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
// INSTANCE ์ฌ์ฉํ๊ธฐ
UserDto userDto = MemberMapper.INSTANCE.UserToDto(user);
2. Dependency Injection ์ฌ์ฉ
DI๋ฅผ ์ง์ํ๋ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๊ตฌํ๋ ๋งคํผ๋ค์ DI๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๋ค.
@Mapper(componentModel = "spring")
public interface MemberMapper {
MemberDto memberToDto(Member member);
}
- ์ง์ํ๋ ํ๋์์ํฌ๋ค
- cdi: ๋งคํผ๋ฅผ application-scoped CDI bean์ผ๋ก ์์ฑํ๋ฉฐ @Inject ๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๋ค.
- spring: ๋งคํผ๋ฅผ ์คํ๋ง ๋น์ผ๋ก ์์ฑํ๋ฉฐ @Autowired ๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๋ค.
- jsr330: @javax.inject.Named, @Singleton์ด๋ ธํ ์ด์ ์ ๋งคํผ์ ๋ถ์ฌ ๋น์ ์์ฑํ๋ฉฐ @Inject ๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๋ค.
@Autowired ์ฌ์ฉํ์ฌ ์์กด์ฑ ์ฃผ์
@Autowired
private MemberMapper mapper;
๊ฒ์ฆ
@Test
public void mapperTest() throws Exception {
User user = repository.findById(1L).get();
UserDto userDto = UserMapper.INSTANCE.userToDto(user);
}
ComponentModel์ Spring์ผ๋ก ์ค์ ํ์์ ๊ฒฝ์ฐ์๋, ์คํ๋ง์ ๋์ DI๋ก ์ฃผ์ ๋ฐ์ ํ์์์ด ์ธ์คํด์ค๋ก ์์ฑํ์ฌ ๋จ์ ํ ์คํธ ํ๋ค.
public class UserDtoMapperTest {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
public void mapperTest(){
...
}
}
๋!
์๋ชป๋ ์ ๋ณด๊ฐ ์๋ค๋ฉด ๋๊ธ์ ํตํด ์๋ ค์ฃผ์ธ์. ๊ฐ์ฌํฉ๋๋ค.
reference.
https://huisam.tistory.com/entry/mapStruct
https://kth990303.tistory.com/131
'Back-end ๋ฐ๋ธ์ฝ์ค > Clone Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[JPA] Entity Custom ID Generator (@GenericGenerator) (0) | 2023.01.18 |
---|
๋๊ธ