Dto 와 Entity 의 변환을 쉽게 도와주는 라이브러리 이다.
변환하기 위해 static 메소드를 만들거나 setter 를 사용하는 경우가 대부분이다.
하지만 MapStruct 라이브러리는 선언만 하면 빌드할 때 자동으로 구현을 해주기 때문에 손쉽게Mapping 처리를 할 수 있다.
gradle 설정
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
먼저 Car 와 CarDto 클래스를 보자
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
//constructor, getters, setters etc.
}
public class CarDto {
private String make;
private int seatCount;
private String type;
//constructor, getters, setters etc.
}
Car 클래스의 numberOfSeats 는 Dto에 seatCount 라는 필드명으로 선언되어있다.
Mapping 인터페이스를 만들어 보자
@Mapper // 1
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); // 2
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car); // 3
}
검증
@Test
public void shouldMapCarToDto() {
//given
Car car = new Car( "Morris", 5, CarType.SEDAN );
//when
CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );
//then
assertThat( carDto ).isNotNull();
assertThat( carDto.getMake() ).isEqualTo( "Morris" );
assertThat( carDto.getSeatCount() ).isEqualTo( 5 );
assertThat( carDto.getType() ).isEqualTo( "SEDAN" );
}
프로젝트에 적용하기
기존의 회원가입 로직에서는 builder 패턴으로 dto -> entity 변환이 이루어지고 있다.
public Long join(MemberJoinDto member) {
Role role = roleRepository.findByName(Authority.ADMIN).orElseThrow(() -> new IllegalArgumentException("not found Roll"));
Member buildMember = Member.builder()
.email(member.getEmail())
.password(passwordEncoder.encode(member.getPassword()))
.roles(Collections.singletonList(role))
.build();
return memberRepository.save(buildMember).getId();
}
Mapper 선언
@Mapper(componentModel = "spring")
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
@Mapping(target = "id", ignore = true) // 1
@Mapping(source = "password", target = "password", qualifiedByName = "encryptPassword")
Member toEntity(MemberJoinDto dto);
@Mapping(source = "email", target = "name") // 3
MemberDto toDto(Member member);
@Named("encryptPassword") // 2
default String encryptPassword(String password) {
return new BCryptPasswordEncoder().encode(password);
}
}
-------------------------------------------
@Getter
@Builder
@AllArgsConstructor
public class MemberJoinDto {
private String email;
private String password;
}
-------------------------------------------
@Getter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberDto {
private String name;
private List<Role> roles;
}
1: dto -> entity 로 변환 하는 과정에 id 값은 자동증가 값이므로 ignore 처리한다.
2: password 는 암호화 되어 데이터베이스에 저장되어야 하기 때문에,
이처럼 로직이 들어가는 매핑 룰을 정할 때는 qualifiedByName 이라는 옵션을 사용한다, @Named 어노테이션으로 선언해놓은 값을 사용하겠다는 의미이다
3: entity -> dto 변환할때 entity의 email 필드를 name 필드로 매핑해준다
dto -> entity 검증
@Override
public Long join(MemberJoinDto member) {
Role role = roleRepository.findByName(Authority.ADMIN).orElseThrow(() -> new IllegalArgumentException("not found Roll"));
Member memberEntity = MemberMapper.INSTANCE.toEntity(member);
memberEntity.setRoles(Collections.singletonList(role));
return memberRepository.save(buildMember).getId();
}
entity -> dto 검증
@Test
public void mapperTest() throws Exception {
Member member = repository.findById(1L).get();
MemberDto memberDto = MemberMapper.INSTANCE.toDto(member);
System.out.println(memberDto);
}