이번에는 MapStruct의 기본적인 사용버에 대해서 알아봅니다.
1. 의존성 설정
dependencies {
...
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// mapstruct
implementation 'org.mapstruct:mapstruct:1.5.5.Final' // ⭐
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' // ⭐
// (optional)lombok - mapstruct binding
implementation 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
...
}
먼저 mapstruct 사용을 위한 디펜던시 설정을 진행합니다. (gradle 기준)
mapstruct는 내부적으로 lombok을 사용하기 때문에, lombok이 필요하고 lombok 디펜던시 아래에 설정해주면 됩니다.
또한, lombok이 제대로 동작하지 않는 문제가 발생할 수 있어 추가적으로 lombok-mapstruct-binding 디펜던시를 추가할 수도 있습니다.
2. 객체 생성
의존성 설정이 완료되었다면, 예시로 사용할 객체를 생성해보겠습니다.
먼저, Entity 객체를 생성합니다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class User {
private Long id;
private String email;
private String name;
private Integer age;
private Address address;
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class Address {
private Long id;
private String location;
private String detail;
}
다음은, Dto 객체를 생성합니다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserDto {
private String email;
private String name;
private Integer age;
private AddressDto address;
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class AddressDto {
private String location;
private String detail;
}
3. 매퍼 생성 및 설정
이제 이 두 객체를 매핑해줄 MapStruct의 매퍼를 생성하게 됩니다.
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
User toEntity(UserDto userDto);
UserDto toDto(User user);
}
위 코드를 자세히 살펴보겠습니다.
먼저, @Mapper 어노테이션을 지정하면 해당 인터페이스가 MapStruct 매퍼임을 알려주게 되고, 컴파일 타임에 해당 인터페이스의 구현체를 MapStruct에서 생성해주게 됩니다.
그리고 @Mapper 어노테이션에는 다양한 설정들을 통해 동작방식을 조정할 수 있습니다.
옵션 | 설명 |
componentModel | 생성된 매퍼 클래스를 어떤 방식으로 관리할지 지정 (예: default, spring, jsr330, cdi) |
unmappedSourcePolicy | 매핑 대상에서 사용되지 않은 소스 필드에 대한 처리 방식 (IGNORE, WARN, ERROR) |
unmappedTargetPolicy | 매핑 대상에 포함되지 않은 필드가 있을 경우의 처리 방식 (IGNORE, WARN, ERROR) |
nullValueMappingStrategy | 소스가 null일 때의 처리 방식 (RETURN_NULL, RETURN_DEFAULT) |
uses | 다른 매퍼 클래스(서브 매퍼)를 참조할 때 사용 |
imports | 매핑 식에서 사용할 외부 클래스들을 import |
typeConversionPolicy | 커스텀 타입 변환 정책 설정 (자주 사용되진 않음) |
해당 코드는, 실제 구현체를 찾아 인스턴스를 반환하는 역할을 수행합니다.
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
그 아래의 추상메서드들은, 객체간의 전환을 명시하며 실제 구현은 MapStruct 에서 생성해주게 됩니다.
User toEntity(UserDto userDto);
UserDto toDto(User user);
이렇게 설정 후, 컴파일을 하게 되면 MapStruct는 실제 구현체를 생성하게 됩니다.
아래는 실제 컴파일 후 생성된 구현체 코드입니다.
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2025-04-04T18:56:28+0900",
comments = "version: 1.5.5.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.10.jar, environment: Java 17.0.14 (Amazon.com Inc.)"
)
public class UserMapperImpl implements UserMapper {
@Override
public User toEntity(UserDto userDto) {
if ( userDto == null ) {
return null;
}
User.UserBuilder user = User.builder();
user.email( userDto.getEmail() );
user.name( userDto.getName() );
user.age( userDto.getAge() );
user.address( addressDtoToAddress( userDto.getAddress() ) );
return user.build();
}
@Override
public UserDto toDto(User user) {
if ( user == null ) {
return null;
}
UserDto.UserDtoBuilder userDto = UserDto.builder();
userDto.email( user.getEmail() );
userDto.name( user.getName() );
userDto.age( user.getAge() );
userDto.address( addressToAddressDto( user.getAddress() ) );
return userDto.build();
}
protected Address addressDtoToAddress(AddressDto addressDto) {
if ( addressDto == null ) {
return null;
}
Address.AddressBuilder address = Address.builder();
address.location( addressDto.getLocation() );
address.detail( addressDto.getDetail() );
return address.build();
}
protected AddressDto addressToAddressDto(Address address) {
if ( address == null ) {
return null;
}
AddressDto.AddressDtoBuilder addressDto = AddressDto.builder();
addressDto.location( address.getLocation() );
addressDto.detail( address.getDetail() );
return addressDto.build();
}
}
이렇게 내부적으로 빌더나 세터 등을 통해서 객체간의 전환을 수행하는 코드를 자동으로 작성해주게 됩니다.
4. 구현체 사용
이제, 해당 구현체를 사용하여 객체간 전환을 수행해보는 단계입니다.
UserDto userDto = UserDto.builder()
.age(19)
.name("김똑똑")
.email("dkdk@ddock.kr")
.address(AddressDto.builder().location("트러스톤빌딩").detail("2층 똑똑").build())
.build();
User user = UserMapper.INSTANCE.toEntity(userDto);
System.out.println(user.toString());
간단하게 UserDto를 하나 생성하였고, 이를 UserMapper에서 선언한 구현체(INSTANCE)를 가져와 toEntity 메서드를 실행시켜 User 객체를 생성하였습니다.
생성 결과를 콘솔에서 확인해보면 아래와같이 잘 생성되는 것을 확인할 수 있습니다.
이제 반대로 한번 해보겠습니다.
User user = User.builder()
.id(1L)
.age(19)
.name("김똑똑")
.email("dkdk@ddock.kr")
.address(Address.builder().id(1L).location("트러스톤빌딩").detail("2층 똑똑").build())
.build();
UserDto userDto = UserMapper.INSTANCE.toDto(user);
System.out.println(userDto.toString());
User 객체를 생성 후, UserMapper 구현체를 통해 UserDto로 변환하였으며 결과는 아래와 같습니다.
이렇게 Entity ↔ Dto 간의 전환 로직 구현을 자동으로 처리할 수 있고, 필드가 많을 수록 더욱 효과적이라는 것을 확인할 수 있었습니다!
'공부 > Java' 카테고리의 다른 글
[MapStruct] 03. 심화 사용법 (0) | 2025.04.07 |
---|---|
[MapStruct] 01. MapStruct란 무엇인가? (0) | 2025.04.07 |
[프랙티컬 모던 자바] 02. 인터페이스 (4) | 2024.11.12 |