Developer Sang Guy

Lombok @Builder 사용 시 MapStruct @AfterMapping 동작 이슈 본문

Java

Lombok @Builder 사용 시 MapStruct @AfterMapping 동작 이슈

은크 2024. 3. 15. 16:06

MappingTarget

@Getter
@ToString(exclude = "orders")
@Table(name = "members")
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicUpdate
public class Member {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String name;
	private Integer age;
	
	@Enumerated(EnumType.STRING)
	private Gender gender;
	
	@Setter
	private String email;
	
	@Column(name = "phone_number")
	private String phoneNumber;
	
	@Column(name = "join_at")
	private LocalDateTime joinAt;
	
	@OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
	private List<Order> orders = new ArrayList<>();
	
	@PrePersist
	private void onJoinAt() {
		this.joinAt = LocalDateTime.now();
	}
	
	@Builder
	public Member(Long id, String name, Integer age, Gender gender, String email, String phoneNumber) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.gender = gender;
		this.email = email;
		this.phoneNumber = phoneNumber;
	}

 

Source

import lombok.Data;

@Data
public class MemberRequest {

	private String name;
	private Integer age;
	private String gender;
	private String email;
	private String phoneNumber;
}

 

Mapper

import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;

import com.ahn.jpa.dto.MemberRequest;
import com.ahn.jpa.model.Member;

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MemberMapper {

	MemberMapper MAPPER = Mappers.getMapper(MemberMapper.class);
	
	Member mapToModel(MemberRequest request);
	
	@AfterMapping
	default void add(MemberRequest request, @MappingTarget Member member) {
		member.setEmail("야호");
	}
}

 

Mapper 구현체

import com.ahn.jpa.dto.MemberRequest;
import com.ahn.jpa.model.Member;
import com.ahn.jpa.model.enums.Gender;
import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-03-15T15:51:10+0900",
    comments = "version: 1.5.3.Final, compiler: Eclipse JDT (IDE) 1.4.50.v20210914-1429, environment: Java 17.0.1 (Eclipse Adoptium)"
)
public class MemberMapperImpl implements MemberMapper {

    @Override
    public Member mapToModel(MemberRequest request) {
        if ( request == null ) {
            return null;
        }

        Member.MemberBuilder member = Member.builder();

        member.age( request.getAge() );
        member.email( request.getEmail() );
        if ( request.getGender() != null ) {
            member.gender( Enum.valueOf( Gender.class, request.getGender() ) );
        }
        member.name( request.getName() );
        member.phoneNumber( request.getPhoneNumber() );

        return member.build();
    }
}

 

위와 같이 @AfterMapping을 설정하였지만 실제 구현된 코드에는 "add()" 메소드가 추가되어 있지 않다.

Builder를 사용할 경우엔 @TargetMapping에 실제 맵핑할 대상이 아닌 대상의 Builder를 추가해줘야 한다.


공식 문서에도 기재되어 있는 내용으로 보인다.
https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after

 

MapStruct 1.5.5.Final Reference Guide

If set to true, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also showWarnings needs to be added due to a problem in the maven-compiler-plugin configuration.

mapstruct.org


Important: when using a builder, the @AfterMapping annotated method must have the builder as @MappingTarget annotated parameter so that the method is able to modify the object going to be build. The build method is called when the @AfterMapping annotated method scope finishes. MapStruct will not call the @AfterMapping annotated method if the real target is used as @MappingTarget annotated parameter.

문서 내용 적용하여 변경된 코드

import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;

import com.ahn.jpa.dto.MemberRequest;
import com.ahn.jpa.model.Member;

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MemberMapper {

	MemberMapper MAPPER = Mappers.getMapper(MemberMapper.class);
	
	Member mapToModel(MemberRequest request);
	
	@AfterMapping
	default void add(MemberRequest request, @MappingTarget Member.MemberBuilder member) {
		member.email("야호");
	}
}

 

변경된 구현체

import com.ahn.jpa.dto.MemberRequest;
import com.ahn.jpa.model.Member;
import com.ahn.jpa.model.enums.Gender;
import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-03-15T16:03:36+0900",
    comments = "version: 1.5.3.Final, compiler: Eclipse JDT (IDE) 1.4.50.v20210914-1429, environment: Java 17.0.1 (Eclipse Adoptium)"
)
public class MemberMapperImpl implements MemberMapper {

    @Override
    public Member mapToModel(MemberRequest request) {
        if ( request == null ) {
            return null;
        }

        Member.MemberBuilder member = Member.builder();

        member.age( request.getAge() );
        member.email( request.getEmail() );
        if ( request.getGender() != null ) {
            member.gender( Enum.valueOf( Gender.class, request.getGender() ) );
        }
        member.name( request.getName() );
        member.phoneNumber( request.getPhoneNumber() );

        add( request, member );

        return member.build();
    }
}

 

Comments