자바생
article thumbnail
Published 2022. 5. 30. 13:48
MapStruct Spring
728x90

0.1. MapStruct

java mapping framework는 여러 가지가 있다

대표적으로 MapStruct와 ModelMapper가 있는데, 대부분 MapStruct를 사용한다

 

  • 아래는 구글 트렌드에서 캡처한 자료

 

그 이유는 “속도” 차이가 월등히 나기 때문이다

ModelMapper는 매핑이 일어날 때 리플렉션이 발생

MapStruct는 컴파일 시점에서 annotation을 읽어 구현체를 만들어내기 때문에 리플렉션이 발생하지 않는다

(리플렉션은 구글링!)

  • modelmapper는 변환할 때마다 맵핑할 객체를 계속해서 만들어내고(런타임), mapstruct는 빌드 시점에 구현체를 하나 만들어서 계속해서 그 구현체를 사용하기 때문에(컴파일) mapstruct가 더 시간이 빠르지 않을까?

 

그렇다면 MapStruct는 어떻게 사용하는지 알아보자

 

0.2. dependency

  • lombok 설치
  • mapstruct 의존성 추가
<java />
implementation 'org.mapstruct:mapstruct:1.4.1.Final' implementation 'org.projectlombok:lombok-mapstruct-binding:0.2.0' annotationProcessor "org.mapstruct:mapstruct-processor:1.4.1.Final" annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0"
  • Java 9와 함께 사용할 수 있다고 한다(map struct 공식 문서)

 

0.3. Member.class

<java />
@Getter @Setter @NoArgsConstructor @ToString(of = {"id", "username", "age"}) @Entity public class Member { @Id @GeneratedValue @Column(name = "member_id") private Long id; private String username; private int age; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "team_id") private Team team; public Member(String username) { this(username, 0); } public Member(String username, int age) { this(username, age, null); } public Member(String username, int age, Team team) { this.username = username; this.age = age; if (team != null) { changeTeam(team); } } }

 

0.4. Team.class

<code />
@Entity @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @ToString(of = {"id", "name"}) public class Team { @Id @GeneratedValue @Column(name = "team_id") private Long id; private String name; @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>(); public Team(String name) { this.name = name; } }

 

0.5. MemberDto.class

<java />
@AllArgsConstructor @Getter public class MemberDto { private String username; private int age; private String teamName; }

 

0.6. MemberMapper.class(연관관계에서 사용)

<java />
@Mapper(componentModel = "spring") public interface MemberMapper{ MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class); @Mapping(target = "teamName", expression = "java(member.getTeam().getName())", ignore = true) MemberDto entityToDto(Member member); @Mapping(target = "team.name", expression = "java(memberDto.getTeamName())") Member dtoToEntity(MemberDto memberDto); }
  • target은 변환할 필드를 의미
  • expression은 연관관계에서 사용할 수 있는데, 중요한 부분은 java로 감싸줘야 한다
  • ignore = true는 해당 값이 null이든 아니든 무시하고 null을 주입한다

 

0.7. MapperTest(Entity -> DTO)

 

<java />
@Test @DisplayName("mapper를 이용하여 Entity를 DTO로 변환") void entityToDTO() throws Exception { //given Member member = new Member("member1", 10, new Team("team1")); //when MemberDto memberDto = MemberMapper.INSTANCE.entityToDto(member); //then assertAll( () -> assertThat(memberDto.getUsername()).isEqualTo(member.getUsername()), () -> assertThat(memberDto.getAge()).isEqualTo(member.getAge()), () -> assertThat(memberDto.getTeamName()).isNull()); }
  • 앞서 말했듯이 ignore=true를 넣으면 team에 값이 있더라도 teamName값이 null이 된다

 

0.7.1. MemberMapperImpl

  • MapStruct가 컴파일 시점에 만들어준 MemberMapper의 구현체이다
  • 코드를 보면 전에 MemberMapper에서 ignore = true 부분 때문에 teanName에 null이 들어간 것을 볼 수 있다

 

0.8. MapperTest(DTO -> Entity)

<java />
@Test @DisplayName("mapper를 이용하여 DTO를 Entity로 변환") void dtoToEntity() throws Exception { //given MemberDto memberDto = new MemberDto("member1", 10, "team1"); //when Member member = MemberMapper.INSTANCE.dtoToEntity(memberDto); //then assertAll( () -> assertThat(memberDto.getUsername()).isEqualTo(member.getUsername()), () -> assertThat(memberDto.getAge()).isEqualTo(member.getAge()), () -> assertThat(memberDto.getTeamName()).isEqualTo(member.getTeam() .getName())); }

 

0.8.1. MemberMapperImpl

  • entityToDto와 다른 점이 무엇일까??
  • dtoToEntity는 생성자를 통해 변환하지만 entityToDto는 setter를 이용해 변환한다
  • Dto는 모든 필드를 인자로 가진 생성자가 존재하고, entity는 그러지 않기 때문이다
  • 즉, 변환할 객체에는 getter가 필수적이고, 변환될 객체에는 모든 필드를 인자로 가진 생성자나 setter가 존재해야 함을 알 수 있다

 

0.9. MemberMapper.class(필드 명이 다를 경우)

<java />
@Mapping(source = "age", target = "memberAge") @Mapping(source = "username", target = "memberName") @Mapping(target = "teamName", expression = "java(member.getTeam().getName())") TeamMemberDto entityToTeamMemberDto(Member member);
  • Member의 필드명과 비교했을 때 다르다
  • 따라서 source는 변환할 객체의 필드명, target은 변환될 객체의 필드명을 의미한다
  • member의 age를 TeamMemberDto의 memberAge에 넣는다는 의미

 

0.9.1. TeamMemberDto.class

<java />
@AllArgsConstructor @Getter public class TeamMemberDto { private String teamName; private String memberName; private int memberAge; }

 

0.9.2. MapperTest

<java />
@Test @DisplayName("entity의 변수명과 Dto의 변수명이 다를 경우의 변환") void entityToAnotherDto() throws Exception { //given Member member = new Member("member1", 10, new Team("team1")); //when TeamMemberDto teamMemberDto = MemberMapper.INSTANCE.entityToTeamMemberDto(member); //then assertAll( () -> assertThat(teamMemberDto.getMemberName()).isEqualTo(member.getUsername()), () -> assertThat(teamMemberDto.getMemberAge()).isEqualTo(member.getAge()), () -> assertThat(teamMemberDto.getTeamName()).isEqualTo(member.getTeam() .getName())); }

 

0.9.3. MemberMapperImpl

  • TeamMemberDto에도 모든 필드를 인자로 가진 생성자가 존재하기 때문에 따로 setter가 필요하지 않다

 

0.10. MemberMapperImpl을 생성한 적이 없는데 이건 무엇인가요?

  • build를 하면  MapStruct가 Mapper Annotation을 가진 인터페이스의 구현체가 생성되고, 빈으로 등록된다
  • 따라서 해당 구현체는 MemberMapper의 추상 메서드를 오버 라이딩해야 하고, 이는 자바 프로퍼티를 이용하여 구현된다
  • 변환할 객체에는 getter가 필수적으로 존재해야 함
  • 변환될 객체에는 모든 필드를 인자로 가진 생성자가 존재하거나, 그것이 아니라면 setter가 존재해야 한다
  • mapper클래스를 빌드하면 generated에 아래와 같은 이름으로 구현체가 생성되는 것을 알 수 있다

 

0.11. 정리 & 생각해야 할 점

  • DTO를 변환시킬 때는 DTO 클래스에 setter가 있거나 모든 인자를 가진 생성자가 있으면 된다(개인적인 생각,, 다만 @Setter보다는 @AllArgs를 선호)
  • 하지만 entity로 변환시킬 때는 entity에 setter를 지양하고, JPA를 사용한 나는 모든 필드를 인자로 가진 생성자를 작성할 수 없다
    • id값을 어떻게 처리해줘야 할까??
  • 그러면 Mapper인터페이스를 생성하고 이를 구현하는 구현체를 직접 생성하여 오버라이딩하면 어떨까?
    • MapStruct를 사용하는 의미가 없어짐
  • update를 사용할 때는 어떻게 할까?
    • Mapper를 이용하여 DTO를 Entity로 변환시킨 뒤, 엔티티에서 Entity를 parameter로 받아 update를 하면 되지 않을까?

 


1. REFERENCES

modelmapper와 mapstruct 성능 비교

java mapping framework 성능 비교

MapStruct 공식문서

 

728x90
profile

자바생

@자바생

틀린 부분이 있다면 댓글 부탁드립니다~😀

검색 태그