스프링과 JPA 기반 웹 애플리케이션 개발 #35 ModelMapper 적용

Jake Seo·2021년 6월 7일
0

스프링과 JPA 기반 웹 애플리케이션 개발 #35 ModelMapper 적용

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


ModelMapper 적용

  • http://modelmapper.org/
    • 객체의 프로퍼티를 다른 객체의 프로퍼티로 맵핑해주는 유틸리티
  • 의존성 추가
<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>2.3.6</version>
</dependency>
  • 토크나이저 설정
modelMapper.getConfiguration()
  .setSourceNameTokenizer(NameTokenizer.UNDERSCORE)
  .setDestinationNameTokenizer(NameTokenizer.UNDERSCORE)

ModelMapper에서의 문제

nested 된 경우, 어떻게 잘라서 읽어야 하는지 등에 대해 주체적으로 정할 수 없다.

기본 설정은 NamingConventions.NONE으로 네이밍 컨벤션 없이 모든 프로퍼티 이름에 적용시키는 것이다.

이를테면 우리는 NotificationsForm 내부에 isStudyCreatedByEmail이라는 필드가 있다. 이 때, ModelMapperStudy 라는 내부적인 클래스에 있는 CreatedByEmail이라는 필드를 찾는지 알 수 없다는 뜻이다. 그래서 뜬금없게 account.setEmail()에 넣을 데이터를 선택하는데 여러 개의 후보지가 발생했다는 에러가 뜬다.

    @Bean
    public ModelMapper modelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration()
                .setDestinationNameTokenizer(NameTokenizers.UNDERSCORE)
                .setSourceNameTokenizer(NameTokenizers.UNDERSCORE);

        return modelMapper;
    }

위와 같이 작성하면, UNDERSCORE(_)가 아니라면 이름이 어떻게 작성되어 있었든 상관없이 그 이름 전체를 하나의 프로퍼티 이름으로 생각한다는 것이다. 만일, nested 한 표현을 하고 싶다면 configuration에 설정해둔대로 UNDERSCORE를 이용하면 된다.

ModelMapper 의존성 추가

        <!-- 모델 맵퍼 -->
        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>2.3.6</version>
        </dependency>

ModelMapper Bean 등록

    @Bean
    public ModelMapper modelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration()
                .setDestinationNameTokenizer(NameTokenizers.UNDERSCORE)
                .setSourceNameTokenizer(NameTokenizers.UNDERSCORE);

        return modelMapper;
    }

어차피 계속 생성자로 생성할 것이 아니기 때문에, 빈을 등록해서 쓰면 편리하다.

.setDestinationNameTokenizer() 메소드를 통해, UNDERSCORE의 경우에만 토크나이징을 하도록 토크나이저를 설정하면 nested 에 대한 프로퍼티를 지정하고 싶을 때는 오직 _를 이용해서 하면 된다. CamelCase가 nested 프로퍼티를 가리킨다는 오해에서 해방된다.

ModelMapper 이용

CASE 1: 이미 인스턴스화된 상태에서의 매핑

수정 전

    public void updateProfile(Account account, Profile profile) {
        account.setUrl(profile.getUrl());
        account.setBio(profile.getBio());
        account.setLocation(profile.getLocation());
        account.setOccupation(profile.getOccupation());
        // TODO: 프로필 이미지
        account.setProfileImage(profile.getProfileImage());
        // TODO: 중요한 문제가 하나 더 남았다.
    }

수정 후

public void updateProfile(Account account, ProfileForm profileForm) {
        // from Source to Destination 로 데이터를 전달해줌
        // 프로퍼티의 이름이 일치한다는 가정 하에서 쓰는 것임
        // 대량의 Setter 를 발생시킨다.
        modelMapper.map(profileForm, account);
    }

간단하게 첫번째 인자에 source를 주고 두번째 인자에 destination을 주면 된다. 내부적으로 대량의 setter를 발생시켜 프로퍼티 이름이 일치시키는 부분을 그대로 옮겨준다.

CASE 2: 인스턴스화되지 않은 상태에서의 매핑

수정 전

model.addAttribute(new ProfileForm(loginAccount));

    public ProfileForm(Account account) {
        this.bio = account.getBio();
        this.url = account.getUrl();
        this.occupation = account.getOccupation();
        this.location = account.getLocation();
        this.profileImage = account.getProfileImage();
    }

수정 후

model.addAttribute(modelMapper.map(loginAccount, ProfileForm.class));

클래스명.class를 이용하여 해당 클래스 정보 자체를 넣어주면, 반환 값이 해당 클래스의 객체가 된다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글