JAVA/소박한그룹 프로젝트

[Spring] MapStruct 수동으로 오버라이딩 코드 구현하기

kth990303 2021. 9. 14. 23:48
반응형

Entity <-> dto 반환 mapper를 알아보던 중, ModelMapper보다 MapStruct가 성능이슈가 적고, 편리하다는 말을 들어 mapstruct를 애용중이다. 간단한 mapstruct 이용법은 아래 포스팅에서 볼 수 있다.

https://kth990303.tistory.com/131

 

[Spring] MapStruct를 이용한 Entity, Dto 반환 및 고찰

그 동안 View layer에서 Entity에 직접적으로 접근하도록 코드를 짰던 나에게, 이번 DTO 적용은 상당히 고된 일이었다. 사실 dto는 단순한 entity의 클론 느낌이라 적용이 크게 어렵지 않을 줄 알았는데,

kth990303.tistory.com

 

그런데, 문제가 생겼다.

도메인이 두 개 이상이며, 도메인끼리 연관관계를 이룰 때, mapstruct 자동매핑이 이루어지지 않는 경우가 발생한 것이다. 예를 들어보자.

 

Post 도메인

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "post_id")
    private Long id;
    private String title;
    private String contents;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
}

Post 테이블과 Member 테이블은 서로 연관관계이다.

연관관계 주인은 Post 테이블이다.

 

DTO는 여러 개가 있다.

게시글 기능에만 집중하는 PostDto에는 title, contents 변수만 포함하며,

postId가 필요한 api의 DTO에는 id, title, contents, member가 필요하다.

그러나, DTO가 엔티티를 직접 참조할 경우, 유지보수 시에 엔티티에 접근할 가능성이 높아지기 때문에 아래와 같이 DTO를 바꿔주었다.

Member 도메인

@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PostIdDto {
    private Long id;
    private String title;
    private String contents;
    private MemberIdDto memberIdDto;

    public PostIdDto(Long id, String title, String contents, MemberIdDto memberIdDto){
        this.id=id;
        this.title=title;
        this.contents=contents;
        this.memberIdDto=memberIdDto;
    }
}

MemberDto를 이용하여 Member 엔티티가 아닌 DTO를 사용하도록 바꿔주었다.

그러나, 이 때 mapStruct를 이용한다면 문제점이 하나 있다.

Entity와 DTO의 필드명이 다르기 때문에 mapstruct 자동매핑이 이루어지지 않는다는 점이다!

또한 mapping으로 설정해주려 해도, toEntity, toDto가 자동으로 이루어지지 않아 결국 수동으로 mapping 오버라이딩 코드를 작성해야한다.

 

참고로 DTO 개수가 여러개이기 때문에, mapper 개수도 여러 개이다.

만약, DTO 개수가 적다면, mapper 개수도 적을 것이다.

 


PostIdMapper 인터페이스

@Mapper(componentModel = "spring")
public interface PostIdMapper extends GenericMapper<PostIdDto, Post> {
    MemberIdMapper memberIdMapper= Mappers.getMapper(MemberIdMapper.class);
    @Override
    default Post toEntity(PostIdDto postIdDto){
        if ( postIdDto == null ) {
            return null;
        }
        String title = postIdDto.getTitle();
        String contents = postIdDto.getContents();
        Member member = memberIdMapper.toEntity(postIdDto.getMemberIdDto());

        Post post = new Post( title, contents, member );
        return post;
    }

    default PostIdDto toDto(Post post){
        if(post==null){
            return null;
        }
        Long id=post.getId();
        String title=post.getTitle();
        String contents=post.getContents();
        MemberIdDto memberIdDto=memberIdMapper.toDto(post.getMember());

        PostIdDto postIdDto=new PostIdDto(id, title, contents, memberIdDto);
        return postIdDto;
    }
}

 

 

결국 이렇게 처리했다.

MemberMapper를 불러와서 MemberDto<->Member Entity 변환해주도록 처리했는데, 잘한 것인지는 모르겠다.

결합도가 높아지지 않을까? 생각했으나, 현재 이 Mapper가 member를 변환시키는 코드가 없기 때문에 Member를 dto<->entity 변환 외에 따로 건드리는 부분이 없는 점, MemberMapper가 postService 로직에도 쓰이고 있는 점을 생각하여 크게 문제되진 않을 것이란 판단 하에 위와 같이 진행하였다.

 

사실 @mapping으로 처리할 수 있지 않을까? 했는데, MemberDto <-> Member 자동 변환이 되지 않아 결국 수동으로 코드를 짤 수 밖에 없었다.

또한, 코드를 짜는데엔 오래 걸리지 않았지만, 설계를 하는 데에 생각보다 시간이 매우 오래 걸려 약 3시간동안 여기에 투자한 것 같다....

 

위와 같이 직접 오버라이딩 메소드를 구현해주면, generated Impl 클래스에는 위 메소드는 생성되지 않는다.

toDtoList, toEntityList 또한 위에서 구현한 메소드를 이용하여 변환해준다.


혹시 몰라 github 주소를 남겨놓겠다.

https://github.com/kth990303/BOJStudyList/blob/master/src/main/java/algopa/study/post/mapper/PostIdMapper.java

 

GitHub - kth990303/BOJStudyList: BOJ 그룹스터디 회원들의 공간을 만들자~

BOJ 그룹스터디 회원들의 공간을 만들자~. Contribute to kth990303/BOJStudyList development by creating an account on GitHub.

github.com

 

반응형