MapStruct란 무엇일까?

Soyun_p·2025년 2월 23일
0

📖 지식

목록 보기
3/10
post-thumbnail

이번에 토이 프로젝트를 하면서 mapper 라는 것을 처음 알게 되었다! 단순히 dto를 entity로, entity를 dto로 바꿔주는 것이다 라고만 알고있었는데 그래도 제대로 알고 쓰는게 좋겠다 싶어서 정리한다.

🪼 MapStruct란?

🔹 MapStruct는 Java 어플리케이션에서 객체 간 매핑을 자동으로 생성해주는 라이브러리이다
🔹 일반적으로 DTO(Data Transfer Object)엔티티(Entity)간의 변환을 위해 사용된다
🔹 MapStruct는 애너테이션 프로세서를 활용하여 컴파일 시점에 매핑 코드를 생성하기 때문에, 런타임 성능 저하 없이 빠르고 안전한 매핑이 가능하다


🪼 MapStruct의 특징

🔹 컴파일 시점 코드 생성: 런타임이 아닌 컴파일 시점에 매핑 코드를 생성하여 성능이 우수하다
🔹 리플랙션 사용 없음: ModelMapper와 달리 리플랙션을 사용하지 않아 실행 속도가 빠르다
🔹 간결한 코드: 복잡한 매핑 로직 없이 간단한 인터페이스 정의만으로 사용 가능하다
🔹 유지보수 용이: 컴파일 타임에 오류를 잡을 수 있어 유지보수가 쉽다

기존 매핑 방식과 비교

매핑 방식장점단점
수동 매핑직접 컨트롤 가능코드량 증가, 유지보수 어려움
ModelMapper코드 최소화런타임 리플렉션 사용으로 성능 저하
MapStruct컴파일 시점 코드 생성, 성능 우수초기 설정 필요

🔹 MapStruct는 수동 매핑보다 코드량이 적고, ModelMapper보다 성능이 뛰어나기 때문에 실무에서 많이 사용된다


🪼 기본 설정

Gradle 설정

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

🪼 기본 사용법

@Mapper
public interface UserMapper{
	UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
    UserDto toDto(User user);
}

@Data
public class User{
	private String name;
    private int age;
}

@Data
public class UserDto{
	private String name;
    private int age;
}

사용 예시:

User user = new User("소윤", 25);
UserDto userDto = UserMapper.INSTANCE.toDto(user);

🪼 고급 기능

1) 필드명이 다른 경우 매핑

@Mapper
public interface CustomUserMapper{
	@Mapping(source = "fullName", target = "name")
    UserDto userDto(User user);
}

2) 여러 개의 소스 객체 매핑

@Mapper
public interface OrderMapper{
	@Mapping(source = "user.name", target = "userName")
    @Mapping(source = "product.name", target = "productName")
    OrderDto toDto(User user, Product product);
}

🪼 MapStruct 과정

1) 인터페이스 정리
먼저 변환할 객체 간 매핑을 정의하는 인터페이스를 만든다

@Mapper(componentModel = "spring")
public interface UserMapper{
	UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    UserDto toDto(User user);
    User toEntity(UserDto userDto);
}

📌 핵심 개념
🔹 @Mapper 애너테이션을 사용해 매퍼 인터페이스를 선언한다
🔹 componentModel = "spring"을 설정하면 Spring의 빈(Bean)으로 등록 가능하다
🔹 INSTANCE = Mappers.getMapper(UserMapper.class); => 싱글톤 객체를 생성한다

2) 컴파일 시점에 구현 클래스 자동 생성
MapStruct는 컴파일 시점에 매핑 코드가 포함된 구현 클래스를 생성한다
위에서 만든 UserMapper 인터페이스의 구현체가 자동으로 만들어진다

✅ Example:

public class UserMapperImpl implements UserMapper {
    @Override
    public UserDto toDto(User user) {
        if (user == null) {
            return null;
        }
        UserDto userDto = new UserDto();
        userDto.setName(user.getName());
        userDto.setAge(user.getAge());
        return userDto;
    }

    @Override
    public User toEntity(UserDto userDto) {
        if (userDto == null) {
            return null;
        }
        User user = new User();
        user.setName(userDto.getName());
        user.setAge(userDto.getAge());
        return user;
    }
}

📌 핵심 개념
🔹 MapStruct가 자동으로 UserMapperImpl 클래스를 만들어서 객체 변환을 수행한다
🔹 리플렉션 없이 정적인 코드라서 성능이 뛰어나다



⬅️내가 만든 게시판에서의 PostMapperImple.java



3) 매핑 메서드 실행(사용하기)
이제 매핑을 실행하면 자동 변환된다

User user = new User("소윤", 25);
UserDto userDto = UserMapper.INSTANCE.toDto(user);

System.out.println(userDto.getName()); //소윤
System.out.println(userDto.getAge()); //25

📌 여기서 하는 일
🔹 UserMapper.INSTANCE.toDto(user) 호출
🔹 UserMapperImpl 클래스의 toDto() 메서드 실행
🔹 User 객체가 UserDto 객체로 변환됨


🪼 실제 사용 예시 (내 게시판 코드)

@Mapper(componentModel = "spring")
public interface PostMapper {
    //from PostCreateDto to Post
    Post toPostFromPostCreateDto(PostCreateDto postCreateDto);

    //from Post ro PostResponseDto
    PostResponseDto toPostResponseDtofromPost(Post post);
}

이런 식으로 PostMapper.java 생성해두고

Post post = postMapper.toPostFromPostCreateDto(postCreateDto);

이렇게 매핑하면서 사용하고 있다!
근데 지금 보니까 살짝 메서드 이름이 별로인 거 같아서 to***로 변경 예정!


🪼 Mappers.getMapper(Class<T> clazz) vs componentModel = "spring"

1) Mappers.getMapper(Class<T> clazz) 방식 (수동 싱글톤)

@Mapper
public interface PostMapper {
    PostMapper INSTANCE = Mappers.getMapper(PostMapper.class);

    Post toPostFromPostCreateDto(PostCreateDto postCreateDto);
    PostResponseDto toPostResponseDtofromPost(Post post);
}

📌 이 방식의 특징
🔹INSTANCE 를 통해 직접 싱글톤 객체를 생성하고 관리한다
🔹Spring 없이도 사용 가능하다 (독립적으로 사용 가능)
🔹Mappers.getMapper(PostMapper.class); 호출 시 MapStruct가 자동으로 구현체를 리턴한다
🔹테스트 코드에서도 편리하게 사용 가능하다

사용 예시

PostMapper postMapper = PostMapper.INSTANCE;
Post post = postMapper.toPostFromPostCreateDto(postCreateDto);

2) `componentModel = "spring" 방식 (Spring 빈으로 관리)

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

    Post toPostFromPostCreateDto(PostCreateDto postCreateDto);
    PostResponseDto toPostResponseDtofromPost(Post post);
}

📌 이 방식의 특징
🔹 Spring이 PostMapper를 싱글톤 빈(Bean)으로 등록하고 관리한다
🔹@Autowired 또는 @Component 가 없어도 Spring이 자동으로 주입한다
🔹 의존성 주입(DI) 가능 => 서비스 레이어에서 쉽게 사용 가능하다
🔹 Spring 컨테이너가 관리하므로 멀티스레드 환경에서도 안전하다

사용 예시

@Service
public class PostService {
    private final PostMapper postMapper;

    @Autowired
    public PostService(PostMapper postMapper) {
        this.postMapper = postMapper;
    }

    public Post createPost(PostCreateDto postCreateDto) {
        return postMapper.toPostFromPostCreateDto(postCreateDto);
    }
}

✅ 결론
🔹 Spring 기반 프로젝트라면 componentModel = "spring"을 사용해서 의존성 주입(DI)를 활용하는 것이 더 좋다!
🔹 Spring을 사용하지 않거나, 독립적인 환경에서 테스트 할 때는 INSTANCE 방식이 더 간편하다!
🔹 멀티스레드 환경에서는 Spring이 관리하는 방식이 더 안정적이다!

0개의 댓글