Java Bean Mapping library ์ ์šฉ - MapStruct

2023-05-26
๐Ÿ“‚ backend > spring
๐Ÿงถ #MapStruct #jpa #java

MapStruct๋กœ dto ๋งคํ•‘ ์ง€์˜ฅ ํƒˆ์ถœ

์ ์šฉ ๋ฐฐ๊ฒฝ

  • ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ์ค‘ ๋ณ€์ˆ˜๊ฐ€ 40๊ฐœ ์ด์ƒ์ด๋˜๋Š” ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•ด์„œ DTO๋กœ ๋ณ€ํ™˜ ํ›„ ๋ฆฌํ„ดํ•ด์ค˜์•ผํ•˜๋Š” API๋ฅผ ๋งŒ๋‚ฌ๋‹ค
  • getter setter ์ข์ข ํƒ€์ดํ•‘ํ•  ์ƒ๊ฐ์— ์ •์‹ ์ด ์•„๋“ํ•ด์ ธ์„œ ๊ตฌ๊ธ€๋ง์œผ๋กœ Entity to Dto ๋ฅผ ๊ฒ€์ƒ‰ํ–ˆ๋‹ค
  • ModelMapper ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ฐœ๊ฒฌ ํ›„ ์ ์šฉํ•ด๋ณด๋ ค๋Š”๋ฐ ๋‚˜๋ž‘ ๋„ˆ๋ฌด ์•ˆ๋งž์•„์„œ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ฐพ์•„๋ณด์•˜๊ณ  ๊ทธ๊ฒƒ์ด MapStruct์ด๋‹ค

MapStruct ์‹œ์ž‘ํ•˜๊ธฐ

  1. ์˜์กด์„ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ build.gradle

    lombok ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ž‘์„ฑ ์ˆœ์„œ๊ฐ€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค ๐Ÿ‘‰ lombok ์ดํ›„์— ์ •์˜

    1dependencies {
    2    ...
    3    implementation 'org.projectlombok:lombok'
    4    annotationProcessor 'org.projectlombok:lombok'
    5    implementation 'org.mapstruct:mapstruct:1.5.3.Final'
    6    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
    7    ...
    8}
  2. Mapper interface ์ž‘์„ฑ

    1import org.mapstruct.Mapper;
    2import org.mapstruct.Mapping;
    3import org.mapstruct.factory.Mappers;
    4
    5@Mapper //์–ด๋…ธํ…Œ์ด์…˜ ์ ์šฉ
    6public interface ConditionsMapper {
    7    ConditionsMapper INSTANCE = Mappers.getMapper(ConditionsMapper.class);
    8    // Mappers.getMapper()๋กœ ์‹ฑ๊ธ€ํ†ค ์ƒ์„ฑํ•˜์—ฌ INSTANCE๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์‚ฌ์šฉ
    9
    10    /* abstract method ์ •์˜
    11     * @Mapping : ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๋ณ€์ˆ˜ ์ด๋ฆ„๋งŒ์œผ๋กœ ์ƒ์„ฑ๋œ getter, setter๋งŒ์œผ๋กœ ๋งคํ•‘์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ๋ฅผ ์ •์˜
    12     *  * source : method์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์ค‘์—์„œ ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งคํ•‘ํ•  ๊ฒƒ์ธ์ง€ ๋ช…์‹œํ•˜๋ฉด ๋จ
    13          ์˜ˆ์‹œ ์ฝ”๋“œ์—์„œ๋Š” method ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ 1๊ฐœ์ด์ง€๋งŒ, 2๊ฐœ ์ด์ƒ์˜ ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ ํ•˜๋‚˜์˜ target ๊ฐ์ฒด๋กœ ๋งคํ•‘ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
    14          ๋‹จ, ํ•˜๋‚˜์˜ @Mapping source ์†์„ฑ์—์„œ ํ•˜๋‚˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ์ฒด๋งŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Œ -> @Mapping ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
    15     *  * target : method์˜ ๋ฆฌํ„ด ํƒ€์ž…
    16     */
    17    @Mapping(source = "condition.equipmentType", target = "equipmentType", qualifiedByName = "equipmentTypeToString")
    18    /* โ˜ source์ชฝ ๊ฐ์ฒด์—์„œ equipmentType์ด๋ผ๋Š” ๋ณ€์ˆ˜์˜ ํƒ€์ž…์ด EquipmentType์ด๋ผ๋Š” Enum๊ฐ์ฒด์ธ๋ฐ,
    19       target์ชฝ ๊ฐ์ฒด์—์„œ๋Š” String type์œผ๋กœ ์ •์˜๋˜์–ด์žˆ๋Š” ๊ฒฝ์šฐ -> ๋ณ„๋„ ์„ค์ • ์—†์œผ๋ฉด Enum name์ด ๊ทธ๋Œ€๋กœ target์œผ๋กœ ๋งคํ•‘๋จ
    20       ์ด ๊ฒฝ์šฐ Enum์—์„œ ๋ณ„๋„๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ fullName์„ ์ง€์ •ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ๊ทธ๊ฒƒ์„ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์˜ˆ๋กœ ๋“ค์—ˆ์Œ */
    21    @Mapping(source = "condition", target = "product", qualifiedByName = "productToBaseProduct")
    22    /* โ˜ source ๊ฐ์ฒด ์ž์ฒด๋ฅผ target ๋ณ€์ˆ˜์— ๋งคํ•‘ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
    23       ์ด ๊ฒฝ์šฐ equipmentType๋ณ„๋กœ ๋งคํ•‘ํ•ด์•ผํ•˜๋Š” Product ํด๋ž˜์Šค๊ฐ€ ๋‹ค๋ฅธ ๊ฒฝ์šฐ๋ฅผ ๊ฐ€์ •ํ•˜๊ณ  ์˜ˆ๋ฅผ ๋“ค์—ˆ์Œ */
    24    @Mapping(source = "condition.param", target = "params")
    25    /* โ˜ ๋‹จ์ˆœํžˆ ๋ณ€์ˆ˜์˜ ์ด๋ฆ„๋งŒ ๋‹ค๋ฅธ ๊ฒฝ์šฐ ์œ„์™€ ๊ฐ™์ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Œ
    26       ์ด ๊ฒฝ์šฐ source์˜ param์€ List<Param>์ด๊ณ  target์— List<ParamDto> params๋กœ ์ •์˜๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๊ฐ€์ •ํ•˜์˜€๊ณ 
    27       MapStruct์—์„œ ์•Œ์•„์„œ ๊ฐ์ฒด, ์ฝœ๋ ‰์…˜ ๋งคํ•‘์— ํ•„์š”ํ•œ ๋ฉ”์†Œ๋“œ๋ฅผ 2๊ฐœ ์ƒ์„ฑํ•จ */
    28    ConditionDto toConditionDto(Condition condition);
    29
    30    //๋ณ€์ˆ˜๋ช…, ํƒ€์ž… ๋“ฑ์—์„œ ํŠน์ด์‚ฌํ•ญ์ด ์—†๋Š” ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”์ƒ๋ฉ”์†Œ๋“œ ์ •์˜๋งŒ ํ•ด๋‘๋ฉด ๋งคํ•‘ ์ฝ”๋“œ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.
    31    ProductChild1 toChild1(Product product);
    32    ProductChild2 toChild2(Product product);
    33
    34    /* static method ๊ตฌํ˜„
    35     * toConditionDto ๋ฉ”์†Œ๋“œ์˜ ์ฒซ๋ฒˆ์งธ @Mapping ์„ค์ • ์ค‘ qualifiedByName ์†์„ฑ์—์„œ ํ˜ธ์ถœํ•˜๊ฒŒ ๋  ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ ์˜์—ญ์ด๋‹ค.
    36     * MapStruct๊ฐ€ ๋งคํ•‘ ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์ค„ ๋•Œ @Named ์–ด๋…ธํ…Œ์ด์…˜์— ์ž‘์„ฑํ•œ ์ด๋ฆ„์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— qualifiedByName์ด๋ž‘ ๋งž์ถฐ์ค˜์•ผํ•จ
    37     */
    38    @Named("equipmentTypeToString")
    39    static String equipmentTypeToString(EquipmentType equipmentType){
    40        return equipmentType.getFullName();
    41    }
    42
    43    @Named("productToBaseProduct")
    44    static BaseProduct productToBaseProduct(Condition condition){
    45        BaseProduct returnProduct = null;
    46        Product product = condition.getProduct();
    47
    48        switch (condition.getEquipmentType()){
    49        case EQUIP1:
    50            returnProduct = INSTANCE.toChild1(product);
    51            break;
    52        case EQUIP2:
    53            returnProduct = INSTANCE.toChild2(product);
    54            break;
    55        }
    56
    57        return returnProduct;
    58    }
    59}
  3. ์ปดํŒŒ์ผ ํ›„ ์ž๋™ ์ƒ์„ฑ๋œ ํด๋ž˜์Šค ๊ตฌ๊ฒฝ

    ๋„ˆ๋ฌด ๊ธธ์–ด์„œ..๐Ÿ‘‰ gist link

  4. 2์—์„œ ์ƒ์„ฑํ•œ INSTANCE๋ฅผ ํ†ตํ•ด ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ

    1public ConditionDto getCondition(Long id){
    2    ...
    3    Condition condition = conditionRepository.findById(id);
    4
    5    return ConditionMapper.INSTANCE.toConditionDto(condition);
    6}

MapStruct์™€ ModelMapper์˜ ์ฐจ์ด์  (=MapStruct๊ฐ€ ๋” ์ข‹์€ ์ด์œ )

  • ModelMapper

    • Collection, Enum ํƒ€์ž…์˜ ๋ณ€์ˆ˜๋ฅผ ๋ณ€ํ™˜ํ•  ๋•Œ ์ž‘์„ฑํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ์žฅํ™ฉํ•จ
    • ๋Ÿฐํƒ€์ž„์—์„œ ์˜ˆ์™ธ๋ฅผ ๋ฐ˜ํ™˜ํ•จ
    • ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๋•Œ ๋””๋ฒ„๊น…์ด ๋ถˆ๊ฐ€๋Šฅํ•จ
  • MapStruct

    • Collection, Enum ํƒ€์ž… ๋ณ€ํ™˜ ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•จ
    • ์ปดํŒŒ์ผํ•  ๋•Œ ์˜ˆ์™ธ๋ฅผ ๋ฐ˜ํ™˜ํ•จ
    • ๋””๋ฒ„๊น…์ด ์‰ฌ์›€ : ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋งคํ•‘ํ•  ๋‚ด์šฉ์„ ์ •์˜ํ•˜๋ฉด ์ปดํŒŒ์ผ ์‹œ getter, setter๋ฅผ ์‚ฌ์šฉํ•œ ํด๋ž˜์Šค๋ฅผ ์ž๋™์ƒ์„ฑํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—
  • Collection type ๋ณ€ํ™˜ ์ฝ”๋“œ ๋น„๊ต

    • ์กฐํšŒ ๊ฒฐ๊ณผ๊ฐ€ findAll์ธ ๊ฒฝ์šฐ
      1// ModelMapper
      2List<User> users = userRepository.getUsers();
      3List<UserDto> userList = users.stream()
      4        .map(user -> modelMapper.map(user, UserDto.class))
      5        .collect(Collectors.toList());
      1// MapStruct
      2UserDto toUserDto(User user);
      3List<UserDto> toUserDtoList(List<User> users);
    • ์กฐํšŒ ๊ฒฐ๊ณผ๊ฐ€ findOne์ธ ๊ฒฝ์šฐ
      1// ModelMapper
      2User user = orderRepository.getOrdersGroupByUser();
      3List<OrderDto> orders = user.getOrders()
      4        .map(order -> modelMapper.map(order, OrderDto.class))
      5        .collect(Collectors.toList());
      6UserDto userDto = modelMapper.map(user, UserDto.class);
      7userDto.setOrders(orders);
      1// MapStruct : findAll์—์„œ ์ž‘์„ฑํ•œ toUserDto๋กœ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
  • Enum type ๋ณ€ํ™˜ ์ฝ”๋“œ ๋น„๊ต

    • Enum

      1@Getter
      2public enum Currency{
      3    WON("๏ฟฆ"),
      4    DOLLAR("$");
      5
      6    private String symbol;
      7
      8    Currency(String symbol){
      9        this.symbol = symbol;
      10    }
      11}
    • ModelMapper

      1@Bean
      2public ModelMapper modelMapper(){
      3    ModelMapper modelMapper = new ModelMapper();
      4
      5    Converter<Currency, String> currencyConverter = ctx -> ctx.getSource().getSymbol();
      6    PropertiMap<Item, ItemDto> itemMap = new PropertyMap<>(){
      7        protected void configure(){
      8            using(currencyConverter).map(source.getCurrency()).setCurrency(null);
      9        }
      10    }
      11    modelMapper.addMappings(itemMap);
      12
      13    return modelMapper;
      14}
    • MapStruct

      1@Mapping(source = "item.currency", target="currency", qualifiedByName = "currencyToSymbol")
      2ItemDto toItemDto(Item item);
      3
      4@Named("currencyToSymbol")
      5static String currencyToSymbol(Currency currency){
      6    return currency.getSymbol();
      7}
  • ์„ฑ๋Šฅ ์ฐจ์ด : MapStruct > ModelMapper
    ์ถœ์ฒ˜ : Java ๋งคํ•‘ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ์„ฑ๋Šฅ

๊ฒฐ๋ก 

๋‚œ ์ด์ œ MapStruct ์—†์ด JPA๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๋ชธ์ด ๋˜์—ˆ๋‹ค. ์šฐ๋ฆฌ๋‚˜๋ผ์—์„œ๋Š” ModelMapper์˜ ์ ์œ ์œจ์ด ๋” ๋†’๋‹ค๊ณ  ํ•˜๋Š”๋ฐ MapStruct ๋งŽ์ด ์“ฐ๋ฉด ์ข‹๊ฒ ๋‹ค.

์ฐธ์กฐ

NHN Cloud Meetup Object Mapping ์–ด๋””๊นŒ์ง€ ํ•ด๋ดค๋‹ˆ?

/end of Java Bean Mapping library ์ ์šฉ - MapStruct
CONTENT LISTMERRI๏ผ‡s DEVELOG
๋กœ์ปฌ์—์„œ minikube๋กœ k8s ๋ง›๋ณด๊ธฐ
2024-10-23