TIL_MapStruct_241219

2024. 12. 20. 01:44·Spring 단기심화 2기

프로젝트 발표 피드백으로 DTO와 Entity 간의 변환 로직이 불필요하게 중복되었다는 말을 들었다. 그에 대한 대안으로 MapStruct라는 것을 소개 받았는데 어떻게 적용할 수 있는지 간단하게 알아보려고 한다.

 

MapStruct

MapStruct는 Java 빈 타입 간의 매핑하는 코드를 자동으로 생성해주는 코드생성기이다. MapStruct는 컴파일 시점에 Java Bean 매핑 코드를 컴파일 시점에 생성하기 때문에 높은 성능을 제공하며, 개발자에게 빠른 피드백을 제공하고 철저한 에러 검사를 수행한다.

User 엔티티

회원가입 API에서 회원 정보를 받아 User Entity로 변환하고 저장된 Entity를 가지고 Response로 변환하는 것을 MapStruct를 적용해보겠다.

@Getter
@Entity
@Table(name = "p_users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@AllArgsConstructor
public class User extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(length = 10, nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(length = 10, nullable = false)
    private String name;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private UserRole role;

    @Column(length = 20, nullable = false)
    private String slackId;
}

MapStruct 적용 전

public record SignupRequest(
        @NotBlank String username,
        @NotBlank String password,
        @NotBlank String name,
        @NotNull UserRole role,
        @NotBlank String slackId
) {
	// RequestDto -> Entity 변환 메서드
        public User toEntity(String encodedPassword){
                return User.builder()
                        .username(username)
                        .password(encodedPassword)
                        .name(name)
                        .role(role)
                        .slackId(slackId)
                        .build();
        }
}
 public record SignupResponse(
        UUID id
) {
 	// Entity -> ResponseDto 변환 메서드
    public static SignupResponse from(User user){
        return new SignupResponse(user.getId());
    }
}

 

회원가입 서비스 로직

@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
    checkDuplicateUsername(signupRequest.username());

    User savedUser = userRepository.save(signupRequest.toEntity(passwordEncoder.encode(signupRequest.password())));
    return SignupResponse.from(savedUser);
}

 

MapStruct 적용 후

의존성 추가

dependencies {
	implementation 'org.mapstruct:mapstruct:1.6.3'
	annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'
}

 

Mapper 인터페이스 및 구현체

@Mapper
public interface UserMapper {

    // UserMapper 인스턴스를 생성 (MapStruct가 자동 생성한 구현체를 반환)
    UserMapper USER_MAPPER = Mappers.getMapper(UserMapper.class);

    // target: 반환 클래스의 필드명
    // source: 매개변수로 전달된 클래스의 필드명
    // target 클래스와 source 클래스 필드명이 같다면 자동으로 매핑
    @Mapping(target = "id", source = "id")
    SignupResponse userToSignupResponse (User user);

    @Mapping(target = "password", source = "encodedPassword")
    User signupRequestToEntity (SignupRequest signupRequest, String encodedPassword);
}
  • 타겟 클래스와 소스 클래스의 필드명이 같다면 자동으로 매핑되지만 필드명이 다를 경우, @Mapping 어노테이션을 사용해 매핑을 지정해야 합니다.
import com.nangman.user.application.dto.request.SignupRequest;
import com.nangman.user.application.dto.response.SignupResponse;
import com.nangman.user.domain.entity.User;
import java.util.UUID;
import javax.annotation.processing.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-12-20T01:01:46+0900",
    comments = "version: 1.6.3, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.11.1.jar, environment: Java 17.0.7 (Azul Systems, Inc.)"
)
public class UserMapperImpl implements UserMapper {

    @Override
    public SignupResponse userToSignupResponse(User user) {
        if ( user == null ) {
            return null;
        }

        UUID id = null;

        id = user.getId();

        SignupResponse signupResponse = new SignupResponse( id );

        return signupResponse;
    }

    @Override
    public User signupRequestToEntity(SignupRequest signupRequest, String encodedPassword) {
        if ( signupRequest == null && encodedPassword == null ) {
            return null;
        }

        User.UserBuilder user = User.builder();

        if ( signupRequest != null ) {
            user.username( signupRequest.username() );
            user.name( signupRequest.name() );
            user.role( signupRequest.role() );
            user.slackId( signupRequest.slackId() );
        }
        user.password( encodedPassword );

        return user.build();
    }
}
  • 내가 작성한 Mapper 인터페이스를 바탕으로 MapStruct가 생성한 변환 코드이다.

회원가입 서비스 로직

import static com.nangman.user.application.mapper.UserMapper.USER_MAPPER;

... 

@Transactional
public SignupResponse signup(SignupRequest signupRequest) {

    checkDuplicateUsername(signupRequest.username());

    User savedUser = userRepository.save(USER_MAPPER.signupRequestToEntity(signupRequest, passwordEncoder.encode(signupRequest.password())));

    return USER_MAPPER.userToSignupResponse(savedUser);
}

 

요청 DTO

public record SignupRequest(
        @NotBlank String username,
        @NotBlank String password,
        @NotBlank String name,
        @NotNull UserRole role,
        @NotBlank String slackId
) {}

 

응답 DTO

public record SignupResponse(
        UUID id
) {}

 


MapStruct를 사용하면 DTO와 Entity 간의 매핑 로직을 Mapper에 위임함으로써 DTO와 Entity 간의 결합도가 낮아지고, 책임이 명확히 분리되어, 코드의 가독성과 유지보수성이 향상된다

저작자표시 비영리 변경금지

'Spring 단기심화 2기' 카테고리의 다른 글

TIL_MapStruct Mapping 오류_250102  (1) 2025.01.03
TIL_Annotation_241221  (1) 2024.12.21
TIL_JVM 구조와 동작 원리_241208  (3) 2024.12.09
TIL_HTTP 메서드의 멱등성_241205  (0) 2024.12.05
TIL_대규모 스트림 처리에서의 데이터 일관성 유지_241205  (0) 2024.12.05
'Spring 단기심화 2기' 카테고리의 다른 글
  • TIL_MapStruct Mapping 오류_250102
  • TIL_Annotation_241221
  • TIL_JVM 구조와 동작 원리_241208
  • TIL_HTTP 메서드의 멱등성_241205
l'avenirJun
l'avenirJun
  • l'avenirJun
    오늘도 꾸준히 개발
    l'avenirJun
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • 📚 개발자의 서재 N
        • 객체지향의 사실과 오해
        • Good Code, Bad Code
        • 도메인 주도 개발 시작하기 N
      • 🔧 트러블 슈팅
      • Java
      • Spring
      • 운영체제
        • 공룡책 학습
      • 알고리즘
      • GIT
      • 면접 지식
      • Spring 단기심화 2기
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    메시지
    객체
    애그리거트
    타입
    객체지향의 사실과 오해
    코드트리
    코딩트리조별과제
    DIP
    협력
    오블완
    good code bad code
    티스토리챌린지
    도메인 모델
    역할
    가독성
    일반화
    모듈화
    리포지터리
    애그리거트 루트
    추상화
    코드 계약
    캡슐화
    유스케이스
    코딩테스트
    책임-주도 설계
    책임
    매핑 구현
    인터페이스
    도메인 주도 개발 시작하기
    specification
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
l'avenirJun
TIL_MapStruct_241219
상단으로

티스토리툴바