ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Map Struct] Map Struct 사용법
    프로그래밍/Map Struct 2021. 12. 16. 22:06
    반응형

    이 글은 혼자 학습한 내용을 바탕으로 작성되었습니다.

    틀리거나 잘못된 정보가 있을 수 있습니다.

    댓글로 알려주시면 수정하도록 하겠습니다.


     

    1. map struct란

    Object Mapping 라이브러리로 특정 객체를 다른 Type의 객체로 변환하는 작업을 해주는 도구 입니다.

     

    Java만을 이용하여 객체의 타입 변환 작업을 진행할 수 있습니다. 하지만 Getter, Setter, Builder 등을 이용하여 객체의 타입 변환 작업을 하면 여러 단점들이 존재합니다.

    • 중복되는 코드가 발생한다.
    • 속성명이 변경되어 Setter, Getter 메소드명이 변경된다. 사용하는 모든 곳의 변경이 발생한다.
    • 비즈니스 로직에 불필요한 로직이 추가된다.
    • 오타와 같은 실수가 발생할 수 있다.

    Map Struct를 이용한다면 이런한 단점들을 해결할 수 있습니다.

     

    2. 의존성 추가

    • Maven
      <dependencies>
          <dependency>
              <groupId>org.mapstruct</groupId>
              <artifactId>mapstruct</artifactId>
              <version>1.4.2.Final</version>
          </dependency>
      </dependencies>
      ...
      <build>
          <plugins>
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <version>3.8.1</version>
                  <configuration>
                      <source>1.8</source> <!-- depending on your project -->
                      <target>1.8</target> <!-- depending on your project -->
                      <annotationProcessorPaths>
                          <path>
                              <groupId>org.mapstruct</groupId>
                              <artifactId>mapstruct-processor</artifactId>
                              <version>1.4.2.Final</version>
                          </path>
                          <!-- other annotation processors -->
                      </annotationProcessorPaths>
                  </configuration>
              </plugin>
          </plugins>
      </build>​
    • Gradle
      dependencies {
          implementation 'org.mapstruct:mapstruct:1.4.2.Final'
          annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
      }​
    • 만약 Lombok과 같이 사용하는 경우
      annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
      annotationProcessor 'org.projectlombok:lombok:1.18.16'
      annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'​
      여기서 mapstruct의 annotation을 먼저 선언 후 lombok annotation을 선언하여야 IDEA에 오류 없이 사용이 가능 합니다.

     

    3. 사용법

    예시에서는 MemberEntity, MemberDTO 클래스를 만들어 사용하였습니다.

     

    • MemberEntity
      @Data
      public class MemberEntity {
          private String name;
      
          private String nickName;
      
          private int age;
      }​
    • MemberDTO
      @Data
      public class MemberDTO {
          private String name;
      
          private String nickName;
      
          private int age;
      }​

     

    이제 MemberEntity에서 MemberDTO로 타입 변환을 시켜주는 Mapper를 생성하여야 합니다.

     

    @Mapper
    public interface MemberMapper {
        MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
    
        MemberDTO entityToDTO(MemberEntity memberEntity);
        MemberEntity DTOToEntity(MemberDTO memberDTO);
    }

    Mapper는 클래스가 아닌 인터페이스 형태로 선언 후 Mapper 어노테이션을 사용하여 해당 인터페이스 Mapper로 사용된다는 것을 표시해 줍니다.

     

    Mapper 구현 클래스를 사용하기 위해 해당 Mapper 타입의 INSTANCE 속성을 선언하고 getMapper를 통해 Mapper 구현 클래스를 가져옵니다.

     

    이후 변환을 하고자 하는 변환 결과 타입을 반환 타입으로 지정하고 메소드 명을 작성하고 Parameter로 변경하고자 하는 타입의 객체를 전달 합니다.

     

    4. 구현 클래스

    여기서 인터페이스가 어떻게 타입 변환을 시켜주는 의문이 생길 수 있습니다.

     

    Map Struct는 빌드 시 인터페이스 타입의 Mapper들을 읽고 해당 인터페이스의 구현 클래스를 자동으로 생성하여 줍니다.

     

    이후 타입 변환을 진행하면 구현 클래스의 구현 메소드를 호출하여 타입 변환을 진행 합니다.

     

    MemberMapper 구현 클래스

    5. 변환 Test

    public class ConstructMappingTest {
        static MemberEntity memberEntity;
        static MemberDTO memberDTO;
    
        @BeforeClass
        public static void setInit(){
            memberEntity = new MemberEntity();
            memberEntity.setName("member");
            memberEntity.setNickName("test_member");
            memberEntity.setAge(25);
    
            memberDTO = new MemberDTO();
            memberDTO.setName("member");
            memberDTO.setNickName("test_member");
            memberDTO.setAge(25);
        }
        @Test
        public void EntityToDTOMapStruct(){
            MemberDTO mappingMemberDTO = MemberMapper.INSTANCE.entityToDTO(memberEntity);
    
            assertThat(memberEntity.getName(), is(mappingMemberDTO.getName()));
            assertThat(memberEntity.getNickName(), is(mappingMemberDTO.getNickName()));
            assertThat(memberEntity.getAge(), is(mappingMemberDTO.getAge()));
        }
    
        @Test
        public void DTOToEntityOMapStruct(){
            MemberEntity memberEntity = MemberMapper.INSTANCE.DTOToEntity(memberDTO);
    
            assertThat(memberDTO.getName(), is(memberEntity.getName()));
            assertThat(memberDTO.getNickName(), is(memberEntity.getNickName()));
            assertThat(memberDTO.getAge(), is(memberEntity.getAge()));
        }
    }

     

     

    MemberEntity를 MemberDTO로 변환 결과 MemberDTO속성 결과

     

    MemberDTO를 MemberEntity로 변환 결과 MemberEntity 속성 결과

     

    테스트 진행 결과 정상적으로 변경이 완료된 것을 알 수 있습니다.

     

    6. Lombok Builder 이용

    Map Struct는 생성자가 아닌 Builder를 이용해서도 타입 변환이 가능 합니다.

     

    Entity 클래스는 보통 불변객체로 만들기 생성자는 모두 Protected로 생성 후 Builder로 객체를 생성하는 경우가 많습니다.

     

    이 경우 생성자가 없으므로 Map Struct는 Builder를 이용하여 객체를 생성하고 값들을 모두 넣어 반환하여 줍니다.

    • Builder MemberEntity
      @Getter
      @Builder
      @NoArgsConstructor(access = AccessLevel.PROTECTED)
      @AllArgsConstructor(access = AccessLevel.PROTECTED)
      public class MemberBuilderEntity {
          private String name;
      
          private String nickName;
      
          private int age;
      }​
    • Builder MemberDTO
      @Data
      @Builder
      @NoArgsConstructor(access = AccessLevel.PROTECTED)
      @AllArgsConstructor(access = AccessLevel.PROTECTED)
      public class MemberBuilderDTO {
          private String name;
      
          private String nickName;
      
          private int age;
      }​
    • Mapper
      @Mapper
      public interface MemberBuilderMapper {
          MemberBuilderMapper INSTANCE = Mappers.getMapper(MemberBuilderMapper.class);
      
          MemberBuilderDTO entityToDTO(MemberBuilderEntity memberBuilderEntity);
          MemberBuilderEntity DTOToEntity(MemberBuilderDTO memberBuilderDTO);
      }​

    Builder 패턴을 이용한 구현 클래스

     

     

    구현 클래스의 메소드를 보면 Builder를 이용하여 타입 변환을 진행하는 것을 볼 수 있습니다.

     

    변환 Test

    public class BuilderMappingTest {
        static MemberBuilderEntity memberEntity;
        static MemberBuilderDTO memberDTO;
    
        @BeforeClass
        public static void setInit(){
            memberEntity = MemberBuilderEntity.builder()
                    .name("member")
                    .nickName("test_member")
                    .age(25)
                    .build();
    
            memberDTO = MemberBuilderDTO.builder()
                    .name("member")
                    .nickName("test_member")
                    .age(25)
                    .build();
        }
    
        @Test
        public void EntityToDTOMapStruct(){
            MemberBuilderDTO mappingMemberDTO = MemberBuilderMapper.INSTANCE.entityToDTO(memberEntity);
    
            assertThat(memberEntity.getName(), is(mappingMemberDTO.getName()));
            assertThat(memberEntity.getNickName(), is(mappingMemberDTO.getNickName()));
            assertThat(memberEntity.getAge(), is(mappingMemberDTO.getAge()));
        }
    
        @Test
        public void DTOToEntityOMapStruct(){
            MemberBuilderEntity memberEntity = MemberBuilderMapper.INSTANCE.DTOToEntity(memberDTO);
    
            assertThat(memberDTO.getName(), is(memberEntity.getName()));
            assertThat(memberDTO.getNickName(), is(memberEntity.getNickName()));
            assertThat(memberDTO.getAge(), is(memberEntity.getAge()));
        }
    }

     

    MemberBuilderDTO를 MemberBuilderEntity로 변환 결과 MemberBuilderEntity속성 결과

     

    MemberBuilderEntity를 MemberBuilderDTO로 변환 결과 MemberBuilderDTO속성 결과

     

    테스트 진행 결과 정상적으로 변경이 완료된 것을 알 수 있습니다.

     

    사용된 예제 코드는 Git에 있습니다.

    반응형

    '프로그래밍 > Map Struct' 카테고리의 다른 글

    [Map Struct] Ambiguous Mapping Error  (0) 2021.12.19

    댓글

Designed by Tistory.