๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Back-end ๋ฐ๋ธŒ์ฝ”์Šค/Clone Project

[Spring] Map Struct ์‚ฌ์šฉํ•˜๊ธฐ

by young-ji 2023. 1. 21.

Map Struct

https://mapstruct.org/    

 

๐Ÿ’ก MapStruct๋Š” ๊ตฌ์„ฑ ์ ‘๊ทผ๋ฒ•์— ๋Œ€ํ•œ ๊ทœ์•ฝ์— ๊ทผ๊ฑฐํ•˜์—ฌ Java Bean ์ข…๋ฅ˜ ๊ฐ„์˜ ๋งคํ•‘ ๊ตฌํ˜„์„ ํฌ๊ฒŒ ๋‹จ์ˆœํ™”ํ•œ code generator ์ด๋‹ค.

 

๊ฐ„๋‹จํ•˜๊ฒŒ ๋งํ•˜๋ฉด Dto ์™€ Entity์˜ ๋ณ€ํ™˜์„ ์‰ฝ๊ฒŒ ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด Map Struct๋ฅผ ์™œ ์‚ฌ์šฉํ• ๊นŒ?

 

 

์žฅ์ 
  1. ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํŒŒ์ผ์‹œ ๋งคํ•‘์ฝ”๋“œ๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. (์ปดํŒŒ์ผ ์‹œ์ ์— ๋งคํ•‘ ์ •๋ณด๊ฐ€ ํƒ€์ž… ์„ธ์ดํ”„ํ•œ ์ง€๋ฅผ ๊ฒ€์ฆ)
  2. ๋ฆฌํ”Œ๋ ‰์…˜์ด ์•„๋‹Œ ์ง์ ‘ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜์—ฌ ์†๋„๊ฐ€ ๋น ๋ฅด๋‹ค.
  3. ๋””๋ฒ„๊น…์ด ์‰ฝ๋‹ค.
  4. ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑ ๋˜๊ธฐ๋•Œ๋ฌธ์— ์ƒ์„ฑ๋œ ๋งคํ•‘ ์ฝ”๋“œ๋ฅผ ๋ˆˆ์œผ๋กœ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

→ ์‹ค์ œ ๊ฑฐ๋Œ€ํ•œ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค๋‹ค๋ณด๋ฉด, ๋‹ค์–‘ํ•œ ๋„๋ฉ”์ธ ์˜์—ญ๋“ค์ด ์žˆ๊ณ  ์ด๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์€ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์žˆ์–ด์„œ ๋งค์šฐ ๋ฒˆ๊ฑฐ๋กญ๊ณ  ์‹ค์ˆ˜ํ•˜๊ธฐ ์ข‹๋‹ค. 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);
}
  1. ๋งคํ•‘ ์ธํ„ฐํŽ˜์ด์Šค์ž„์„ ๋ช…์‹œํ•œ๋‹ค.
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋งคํผ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์ค€๋‹ค. (์—ฌ๊ธฐ์„œ Instance๊ฐ€ UserMapper๋ฅผ ์ƒ์†๋ฐ›์•„์„œ UserMapperImpl๋ฅผ ๊ตฌํ˜„ํ•ด์ค€๋‹ค. )
  3. UserDto → User ๋ฉ”์†Œ๋“œ
    • User์—๋งŒ ์žˆ๋Š” id ํ•„๋“œ๋ฅผ ์ƒ์ˆ˜ "0L"๋กœ ์ง€์ •ํ•œ๋‹ค. 
    •  ( id ์ƒ์„ฑ ์ „๋žต์ด Auto Increment์—ฌ์„œ ๊ฐ’์„ ๋Œ€์ž…ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์„๋•Œ๋Š” @Mapping(target = "id", ignore = true) ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. )
    • UserDto์˜ created ํ•„๋“œ๋ฅผ User์— createdTime ํ•„๋“œ๋กœ ์ €์žฅ
    • UserDto์—๋งŒ ์žˆ๋Š” email ํ•„๋“œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฌด์‹œ๋œ๋‹ค.
  4. 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์— ๋งคํ•‘ํ•œ๋‹ค.

https://stackoverflow.com/questions/43518374/mapping-a-dto-in-a-dto-to-entity-in-entity-with-mapstruct   

 

 

 

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

https://wise-develop.tistory.com/18 

https://ykh6242.tistory.com/entry/%EB%B0%98%EB%B3%B5%EC%A0%81%EC%9D%B8-DTO-%EB%B3%80%ED%99%98-%EC%9E%91%EC%97%85%EC%9D%84-%ED%95%9C-%EB%B2%88%EC%97%90-%EC%A0%95%EC%9D%98-MapStruct-%EA%B8%B0%EB%B3%B8-%EC%A0%95%EB%A6%AC

'Back-end ๋ฐ๋ธŒ์ฝ”์Šค > Clone Project' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[JPA] Entity Custom ID Generator (@GenericGenerator)  (0) 2023.01.18

๋Œ“๊ธ€