이번에 토이 프로젝트를 하면서 mapper 라는 것을 처음 알게 되었다! 단순히 dto를 entity로, entity를 dto로 바꿔주는 것이다 라고만 알고있었는데 그래도 제대로 알고 쓰는게 좋겠다 싶어서 정리한다.
🔹 MapStruct는 Java 어플리케이션에서 객체 간 매핑을 자동으로 생성해주는 라이브러리이다
🔹 일반적으로 DTO(Data Transfer Object)와 엔티티(Entity)간의 변환을 위해 사용된다
🔹 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);
}
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이 관리하는 방식이 더 안정적이다!