ModelMapper 2편 외 기타 개선한 것- TypeMap
ModelMapper PropertyMap 매핑 결국은…
저번에 PropertyMap를 사용하여 필드값들을 매핑시켜주는 방법으로 바꾼 후, 여태보지 못한 에러가 나타나게 되어 다시 머리를 굴리게 된다.
자세히 이 에러가 나타난 배경은 서버를 구동시킨 웹페이지에서 DTO를 이용한 CRUD 기능을 한번 실행하고 난 다음 부터는 웹페이지 콘솔에는 500에러가 출력이 되고, IDE 로그에서는 위의 로그가 출력되었다.
로그에 출력되어 있는 에러를 보았을 때 PropertyMap를 Service 안에 있는 각각의 crud 메서드안에 선언을 해주었기에 두번째부터 실행 하였을 때 이미 첫번째에 선언한 PropertyMap 매핑이 이미 존재하고 있기에 이러한 에러가 나타난 것이라 추측하였다.
그래서 검색을 하였을 때 국내에서 이러한 에러를 겪은 사례도 찾기 어려워 결국 외국에는 어떻게 처리하나 범위를 넓혀서 검색하였는데 나의 추측이 전반적으로 맞았던 같다. ModelMapper가 메소드를 호출때마다 이 클래스의 새 개체가 생성될 때 초기화가 수행되어 PropertyMap 매핑 까지 새롭게 생성할려고 했던 것이다. 그리하여 고친 코드는 이러하다.
- 고치기 전
1 2 3 4 5 6 7 8 9 10 11
PropertyMap<ReplyDTO, Reply> replyMapping = new PropertyMap<ReplyDTO, Reply>() { protected void configure() { map().getBoard().setBno(source.getBno()); } }; PropertyMap<Reply, ReplyDTO> ReplyReadMapping = new PropertyMap<Reply, ReplyDTO>() { protected void configure() { map().setBno(source.getBoard().getBno()); } };
- 고친 후
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
static { PropertyMap<ReplyDTO, Reply> replyMapping = new PropertyMap<ReplyDTO, Reply>() { protected void configure() { map().getBoard().setBno(source.getBno()); } }; modelMapper.addMappings(replyMapping); } static { PropertyMap<Reply, ReplyDTO> ReplyReadMapping = new PropertyMap<Reply, ReplyDTO>() { protected void configure() { map().setBno(source.getBoard().getBno()); } }; modelMapper.addMappings(ReplyReadMapping); }
crud 메서드에 각각 선언한 PropertyMap 매핑을 지우고 static(클래스) 전역변수로 선언하여 첫 실행 하였을 때 하나만 생성하여 Service클래스의 여러 메소드로 여러번 호출하더라도 새롭게 매핑이 생성을 시도하지 않을 것이다.
TypeMap
이렇게 PropertyMap매핑의 오류는 고쳤지만, 오히려 해결되면서 마음에 들지 않는 점들이 생겼다.
첫번째는 이러한 오류를 해결하기 위해서 새로운 코드들을 추가하여 코드양은 많아졌고, 두번째는 내가 겪은 PropertyMap 오류들을 내가 아직 실력이 부족해서 그런건지 어떻게 해결했는지 국내에서 찾을 수 없었고 해외에서 찾을 수 밖에 없는데 심지어 해외에도 별 사례가 없었기에 이 방법이 비효율적인가 싶었다.
그래서 다른 방법이 있을까 다시 한번 찾아본 결과 TypeMap기능을 사용하는 방법이 있었다.
TypeMap 인터페이스를 구현함으로써 매핑 설정을 하여, 앞서 PropertyMap매핑을 했던 것처럼 ModelMapper객체의 매핑 관계를 설정해 줄 수 있다.
1
2
3
4
5
6
modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper -> {
mapper.map(Source::getA, Destination::setA);
mapper.map(Source::getB, Destination::setB);
});
Destination dest = modelMapper.map(source, Destination.class);
이런식으로 TypeMap 매핑을 설정해줄 수 있다. 그렇다면 내가 원하는 매핑 전략은 이러하다.
- ReplyDTO.bno(long) -> Reply.board(Board).bno(long)
- Reply.board(Board).bno(long) -> ReplyDTO.bno(long)
source의 타입과 destination의 타입이 일치하지 않아서 PropertyMap매핑 기능을 사용하기 전에는 애가 먹었는데 다시 새롭게 검색해본 결과 아래 구조처럼 설정하면 ource와 destination 사이에서 deep mapping이 발생하여 매핑이 가능하다.
1
2
3
modelMapper.typeMap(ReplyDTO.class, Reply.class).addMapping(
src -> src.getBno(), (dest, v) -> dest.getBoard().setBno((Long) v)
);
PropertyMap매핑 사용했을 때와 TypeMap 매핑을 사용했을 때와 비교를 하면 코드량이 확연히 차이가 나는 것을 보면서 위에 생각했던 문제점들을 TypeMap기능을 사용하면서 해결하였다.
- PropertyMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
@Service @RequiredArgsConstructor @Transactional @Log4j2 public class ReplyServiceImpl implements ReplyService{ private final ReplyRepository replyRepository; private static final ModelMapper modelMapper = new ModelMapper(); static { PropertyMap<ReplyDTO, Reply> replyMapping = new PropertyMap<ReplyDTO, Reply>() { protected void configure() { map().getBoard().setBno(source.getBno()); } }; modelMapper.addMappings(replyMapping); } static { PropertyMap<Reply, ReplyDTO> ReplyReadMapping = new PropertyMap<Reply, ReplyDTO>() { protected void configure() { map().setBno(source.getBoard().getBno()); } }; modelMapper.addMappings(ReplyReadMapping); } @Override public Long register(ReplyDTO replyDTO){ Reply reply = modelMapper.map(replyDTO, Reply.class); return replyRepository.save(reply).getRno(); } @Override public ReplyDTO read(Long rno){ Optional<Reply> result = replyRepository.findById(rno); Reply reply = result.orElseThrow(); ReplyDTO replyDTO = modelMapper.map(reply, ReplyDTO.class); return replyDTO; }
- TypeMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
@RequiredArgsConstructor
@Transactional
@Log4j2
public class ReplyServiceImpl implements ReplyService{
private final ReplyRepository replyRepository;
private final ModelMapper modelMapper;
@Override
public Long register(ReplyDTO replyDTO){
modelMapper.typeMap(ReplyDTO.class, Reply.class).addMapping(
src -> src.getBno(), (dest, v) -> dest.getBoard().setBno((Long) v)
);
Reply reply = modelMapper.map(replyDTO, Reply.class);
return replyRepository.save(reply).getRno();
}
@Override
public ReplyDTO read(Long rno){
modelMapper.typeMap(Reply.class, ReplyDTO.class).addMapping(
src -> src.getBoard().getBno(), (dest, v) -> dest.setBno((Long)v)
);
Optional<Reply> result = replyRepository.findById(rno);
Reply reply = result.orElseThrow();
ReplyDTO replyDTO = modelMapper.map(reply, ReplyDTO.class);
return replyDTO;
}
주제 밖 이야기 - BooleanExpression왜 null이..
스프링 서버를 실행하는데 BooleanExpression쪽에서 null 오류가 출력되면서 분명 리턴값이 null이면 무시한다고 알고 있었는데 자세히 보니 리턴값이 null이 아니라 BooleanExpression 으로 선언한 조건 메서드에서 매개변수가 null이라 일어나는 오류였다.
그래서 조건 메서드에 매개변수가 null이 들어오면 리턴값 또한 null을 주는 if절을 추가하여 해결하였다.
1
2
3
4
5
6
7
8
private BooleanExpression contentSearch(String[] types, String keyword){
if(types == null){///추가한 if절
return null;
}
else{
return Arrays.asList(types).contains("c") == true? board.content.contains(keyword) : null;
}
}