Introduction
프로젝트를 진행하던 중 타입이 다른 두 객체간에 ModelMapper로 매핑을 할 일이 생겼다.
public class Person { ... private String images; }
public class PersonDto { ... private List<String> images; }
Entity의 Images는 String 타입으로 여러 이미지 파일 이름을 ;를 구분자로 사용하여 저장하고, Dto는 String 을 Split 하여 리스트로 변환하여 응답해야 하는 상황이다.
즉,
"a;b;c;d" -> ["a", "b", "c", "d"]
위와 같이 매핑하는것이 목표이다.
Solution
ExpressionMap과 Converter를 사용하면 된다. ExpressionMap은 람다 식을 이용하여 필드 이름이 다른 매칭 정의나, 일치하지 않는 타입에 대한 매핑을 정의할 수 있으며, Converter는 단순 매핑이 아닌 커스텀된 데이터의 매핑이 가능해진다.
@Bean public ModelMapeer modelMapper() { ModelMapper modelMapper = new ModelMapper(); modelMapper.typeMap(Person.class, PersonDto.class).addMappings(mapper -> { mapper.using(imagesConverter()) .map(Person::getImages, PersonDto::setImages); }); return modelMapper; } private Converter<String, List<String>> imagesConverter() { return context -> { if (context.getSource() == null || context.getSource().isEmpty()) { return null; } return Arrays.stream(context.getSource().split(";")).toList(); } )
- typeMap 을 통해 Source Class와, Destination Class 를 지정한다.
- addMappings() 메서드로 ExpressionMap을 추가한다. - mapper 람다식
- ExpressionMap에 Converter를 추가한다. - mapper.using()
- map()으로 필드 매핑을 지정한다.
Converter는 AbstractConverter나 Converter를 구현하면 되는데, 람다식을 이용하면 간편하다.
람다식의 context는 MappingContext로 지정된 타입의 Source, SourceType, Destination, DestinationType 등을 불러올 수 있다.
context의 source 인 String 이 null이나 Empty 상태가 아니라면 ; 구분자로 Split 후 리스트로 변환하여 리턴해 주었다.
주의할 점은 Converter를 추가하면 해당 세팅이 우선시 되기 때문에 ModelMapper의 setSkipNullEnabled 옵션이 true 이더라도 매핑을 시도하게 된다. 그러면 Converter 코드가 실행되다가 NPE 혹은 다른 예외를 발생시키게 된다.
ExpressionMap 의 Skip 또한 마찬가지이다. 따라서 Converter 구현 코드에 반드시 null 체크 혹은 다른 예외 체크를 추가해 주어야 한다.
Test
@Autowired private ModelMapper modelMapper; @Test void customMapperTest() { Person person = new Person(); person.setId(1); person.setName("Lee"); person.setAge(30); person.setImages("a;b;c;d"); PersonDto personDto = modelMapper.map(person, PersonDto.class); System.out.println(person); System.out.println(personDto); assertThat(personDto.getId()).isEqualTo(person.getId()); assertThat(personDto.getName()).isEqualTo(person.getName()); assertThat(personDto.getImages().size()).isEqualTo(4); } @Test void nullImagesTest() { Person person = new Person(); PersonDto personDto = modelMapper.map(person, PersonDto.class); assertThat(personDto.getImages()).isNull(); }
Person(id=1, age=30, name=Lee, images=a;b;c;d) PersonDto(id=1, age=30, name=Lee, images=[a, b, c, d])
Link
'개발 > Spring Boot' 카테고리의 다른 글
[Spring Boot] Spring cache 를 사용해 보자 (1) | 2024.02.07 |
---|---|
[Spring Boot] Spring Boot Application 을 재시작해도 Spring cache가 초기화 되지 않는 문제 해결 (0) | 2024.02.07 |
[SpringBoot] 발급받은 타행 SSL인증서를 SpringBoot에 적용시키기 (0) | 2022.12.10 |