티스토리 뷰

Java/Mapstruct

[Mapstruct] @Mapping 활용하기

종벌🍀 2024. 2. 27. 09:45
반응형

 

 

 

 



 

 

 

SpringBoot

 

 

Mapstruct 를 활용하면 Entity ↔ DTO 간 필드들을 쉽게 맵핑하여 사용할 수 있습니다.

필드가 많아지면 일일이 손수 맵핑하는게 여간 귀찮은 일이 아니며,

생각없이 맵핑하다가 타이핑 실수로 맵핑이 안되는 경우가 발생 할 수 있기 때문에

Mapstruct 를 잘 활용하는 것이 좋습니다.

 

 

이번 포스트에서는 mapstruct 로 간단히 entity 와 DTO 간 필드를 맵핑하는 법과

@Mapping 어노테이션을 활용하여 필드 데이터를 조작 맵핑하는 방법을 다뤄보려고 합니다.

 

더 고급 기술들이 있지만 저도 아직 실 사용하지 않는터라 다 다루지는 않겠습니다.

 

 

 

 

 

 

 


Mapstruct 를 사용하기 위해서는 아래의 dependency 를 추가해줍니다.

implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'

 

 

 

 

mapstruct 인터페이스를 작성 후 빌드하면 build 폴더에 구현체가 생성됩니다.

@Mapper(componentModel = "spring")
public interface SampleToMapper {
  
  SampleDTO toDTO(SampleEntity e);
  
  SampleEntity toEntity(SampleDTO dto);
}

 

 

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-02-23T17:13:49+0900",
    comments = "version: 1.5.5.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-7.6.jar, environment: Java 1.8.0_144 (Oracle Corporation)"
)
@Component
public class SampleToMapperImpl implements SampleToMapper {

    @Override
    public SampleDTO toDTO(SampleEntity e) {
        if ( e == null ) {
            return null;
        }

        SampleDTO sampleDTO = new SampleDTO();

        sampleDTO.setGuid( e.getGuid() );
        sampleDTO.setUserName( e.getUserName() );
        sampleDTO.setUserEmail( e.getUserEmail() );

        return sampleDTO;
    }
    
    @Override
    public SampleEntity toEntity(SampleDTO d) {
        if ( d == null ) {
            return null;
        }

        SampleEntity.SampleEntityBuilder sampleEntity = SampleEntity.builder();

        sampleEntity.userName( d.getUserName() );
        sampleEntity.userEmail( d.getUserEmail() );
        sampleEntity.guid( d.getGuid() );

        return sampleEntity.build();
    }
}

 

 

DTO에는 @Data 어노테이션 사용으로 setter로 맵핑해주고

Entity 에는 @setter 사용을 지양하기 때문에 @builder를 사용해서 필드를 맵핑해줍니다.

 

 

 

 

 

이렇게 해서 서비스 단에서 DTO ↔ Entity 간 필드를 손쉽게 맵핑할 수 있습니다.

private final SampleToMapper sampleToMapper;


SampleDTO dto = sampleToMapper.toDTO(sampleEntity);
	...
SampleEntity entity = sampleToMapper.toEntity(sampleDTO);
	...

 

 

 

 

 

 

 

 


위와 같이 선언한 필드명이 동일할 경우에는 mapstruct가 알아서 맵핑을 잘해주는데

DTO 와 Entity 간 필드명이 상이할 경우에는 맵핑이 되질 않습니다.

이럴 때 @Mapping 어노테이션을 사용하여 이 필드는 이 필드로 맵핑해라 라고 명시해줘야 합니다.

 

 

만약 아래와 같이 DTO와 Entity 가 있다면 @Mapping 어노테이션을 활용합니다.

 

@Data
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ScheduleDTO {

    private String start;
    private String end;
    private String title;
    private String id;

    private Boolean allDay;
    private String customHtml;

 

@Entity
@Getter
@NoArgsConstructor
@DynamicInsert
public class ScheduleEntity {

    @Column(name = "START_DATE")
    private String startDate;
    @Column(name = "END_DATE")
    private String endDate;
    @Column(name = "TITLE")
    private String title;
    @Column(name = "REG_DATE", updatable = false)
    private LocalDateTime regDate;
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "UUID")
    private String uuid;

 

 

날짜필드와 id 필드명이 다릅니다 

이럴경우 필드명이 다르기 때문에 서로 맵핑이 되질 않습니다.

 

이럴때 아래와 같이 명시를 해주면 됩니다.

 

@Mapper(componentModel = "spring")
public interface ScheduleToMapper  {

    @Mapping(target = "id", source = "uuid")
    @Mapping(target = "start", source = "startDate")
    @Mapping(target = "end", source = "endDate")
    @Mapping(target = "customHtml", expression = "java(ScheduleEntity.setCustomHtml(e))")
    @Mapping(target = "allDay", constant = "true")
    ScheduleDTO toDTO(ScheduleEntity e);

    @Mapping(target = "uuid", source = "id")
    @Mapping(target = "startDate", source = "start")
    @Mapping(target = "endDate", source = "end")
    ScheduleEntity toEntity(ScheduleDTO d);
}

 

 

리턴받는 쪽이 target 이고 인자쪽이 source 입니다.

 

이렇게 하면 필드명이 달라도 명시된 대로 맵핑이 올바르게 되는 것을 보실 수 있습니다.

 

@Override
public ScheduleDTO toDTO(ScheduleEntity e) {
    if ( e == null ) {
    	return null;
    }

    ScheduleDTO.ScheduleDTOBuilder ScheduleDTO = ScheduleDTO.builder();

    ScheduleDTO.id( e.getUuid() );
    ScheduleDTO.start( e.getStartDate() );
    ScheduleDTO.end( e.getEndDate() );
    ScheduleDTO.title( e.getTitle() );
    
    ScheduleDTO.customHtml( ScheduleEntity.setCustomHtml(e) );
    ScheduleDTO.allDay( true );

    return ScheduleDTO.build();
}

@Override
public ScheduleEntity toEntity(ScheduleDTO d) {
    if ( d == null ) {
        return null;
    }

    ScheduleEntity.ScheduleEntityBuilder ScheduleEntity = ScheduleEntity.builder();

    ScheduleEntity.startDate( d.getStart() );
    ScheduleEntity.endDate( d.getEnd() );
    ScheduleEntity.title( d.getTitle() );
    ScheduleEntity.uuid( d.getId() );

    return ScheduleEntity.build();
}

 

 

빌드된 인터페이스 구현체를 보면 필드명이 다르지만 @Mapping 어노테이션으로 명시를 해줬기 때문에

잘 맵핑 해준 것을 볼 수 있습니다.

 

 

 

 

 

 


그 외에도 맵핑을 무시하는 ignore 옵션도 있고

값이 null 일때 디폴트값을 지정하는 defaultValue 옵션도 있고

아예 맵핑할때 값을 박아버리는 constant 옵션도 있습니다.

@Mapping(target = "allDay", constant = "true")

 

expression 으로 target 에 특정 로직을 적용할 때 사용합니다.

날짜 포맷팅을 한다던가 코드값에 따라 코드네임을 반환한다던가 그런 경우 사용합니다.

@Mapping(target = "customHtml", expression = "java(CampaignScheduleEntity.setCustomHtml(e))")

 

저는 조회한 데이터의 일부를 가지고 HTML 로 만들어줘야 하는 경우여서 

Entity에 static method 를 하나 작성해서 사용했습니다.

 

mapstruct 가 맵핑하면서 일부 필드들에 데이터를 커스텀하여 넣을 수도 있어서 

조회 후 필드들에 직접 커스텀 로직을 안태워도 되서 DTO ↔ Entity 간 맵핑의 번거로움을 많이 덜어낼 수 있었습니다.

 

 

 

 

 

 

 



 

 

반응형
댓글
반응형
최근에 올라온 글
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Total
Today
Yesterday