이전 글인 DTO의 개념을 포함하고 있습니다.
2022.06.28 - [CodeStates/Spring MVC] - [자바 Spring] DTO에 대해 배워보자
[자바 Spring] DTO에 대해 배워보자
DTO(Data Transfer Object)란 계층간 데이터 교환을 위해 사용하는 객체(Java Beans)입니다. DTO 중 T인 Transfer라는 의미에서 알 수 있듯이 데이터를 전송하기 위한 용도의 객체 정도로 생각할 수 있습니다.
jungdo8016.tistory.com
Mapper은 쉽게 말해 하나의 클래스를 특정 클래스의 형태로 변환해 주는 것을 말합니다. 예를 들어 보겠습니다.
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
return new ResponseEntity<>(memberDto, HttpStatus.CREATED);
}
위 코드는 이전 작성 글인 "DTO에 대해 배워보자"에서 살펴보았던 Controller 클래스에 DTO를 적용한 예시입니다. 그리고 아래 코드는 MemberPostDto 클래스 내부 코드입니다.
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
public class MemberDto {
@NotBlank
@Email
private String email;
@NotBlank(message = "이름은 공백이 아니어야 합니다.")
private String name;
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
private String phone;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
이전 글에서 그대로 다뤘던 코드들이랑 익숙하실 겁니다.
본론부터 말하자면 저는 위의 DTO 클래스를 비즈니스 로직을 처리하기 위한 클래스, 도메인 엔티티(Entity) 클래스로 변환시킬 예정입니다.
위 같은 DTO 클래스를 통해 연결되어 있는 Controller 클래스를 우리는 API 계층이라고 말합니다. 클라이언트의 Request Body를 전달 받고 클라이언트에게 되돌려 줄 응답 데이터를 담는 역할을 하기 떄문이죠
이러한 API 계층에서 전달 받은 요청 데이터를 기반으로 서비스 계층에서 비즈니스 로직을 처리하기 위해 필요한 데이터를 전달 받고, 비즈니스 로직을 처리한 후에는 결과 값을 다시 API 계층으로 리턴해주는 역할을 수행하는 것을 도메인 엔티티(Entity) 클래스라고 합니다.
위 그림에서 Controller를 Api 계층, 도메인 엔티티 클래스를 Service 계층이라고 말합니다. 그리고 그 사이에서 일어나는 데이터 교환 방식이 DTO 클래스인 것이죠
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
private long memberId;
private String email;
private String name;
private String phone;
}
위 Member 클래스가 엔티티 클래스 입니다. 먼저 에노테이션(@)에 대해 간단히 설명하자면
- @Getter : 클래스 내 필드들의 모든 Getter 메소드를 생성합니다.
- @Setter: 클래스 내 필드들의 모든 Setter 메소드를 생성합니다.
- @NoArgsConstructor : 기본 생성자를 생성합니다.
- @AllArgsConstructor :주어진 필드들을 모두 포함하는 생성자를 생성합니다.
전체적인 흐름을 다시 정리하자면 다음과 같습니다.
1.사용자가 브라우저에 접속하여 필요한 정보들을 서버에 전달
2. Controller 클래스 내부에 있는 DTO가 내부 각각의 변수들에 정보들을 저장한다
3. 위 Member 클래스, 즉 엔티티 클래스에게 Mapper을 이용하여 다시 저장된 변수값들을 전달한다.
4. 값들을 가지고 있는 Member 클래스를 이용하여 서비스 클래스에서 필요한 비즈니스 로직을 수행하고 다시 Mapper을 통해 반환
즉, Mapper의 역할은 - - -> [매퍼(Mapper)를 이용한 DTO 객체 ↔ 엔티티(Entity) 객체 매핑]
이제 드디어 Mapper를 살펴보겠습니다. Mapper을 만들 때는 개발자가 수동으로 만들 수도 있지만 시간도 오래걸리고 비효율적입니다. 그래서 등장한 것이 MapStruct입니다. MapStruct는 매퍼 클래스를 자동으로 구현해 줌으로써 개발자의 생산성을 향상시켜 줄 수 있는 생성기입니다.
MapStruct는 DTO 클래스처럼 Java Bean 규약을 지키는 객체들 간의 변환 기능을 제공하는 매퍼(Mapper) 구현 클래스를 자동으로 생성해주는 코드 자동 생성기입니다.
1. MapStruct 의존 라이브러리 설정
MapStruct 기반의 매퍼(Mapper)를 자동 생성하기 위해서는 MapStruct 관련 의존 라이브러리를 Gradle의 build.gradle 파일에 아래와 같이 추가해야합니다.
dependencies {
...
...
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
[MapStruct 인터페이스 코드]
package com.codestates.member.mapstruct.mapper;
import com.codestates.member.dto.MemberPatchDto;
import com.codestates.member.dto.MemberPostDto;
import com.codestates.member.dto.MemberResponseDto;
import com.codestates.member.entity.Member;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring") // (1)
public interface MemberMapper {
Member memberPostDtoToMember(MemberPostDto memberPostDto);
MemberResponseDto memberToMemberResponseDto(Member member);
}
해당 코드에서 @Mapper 애너테이션은 해당 인터페이스를 MapStruct의 매퍼 인터페이스로 정의하는 역할을 수행합니다.
@Mapper 애너테이션의 애트리뷰트로 componentModel = "spring"을 지정해주면 Spring의 Bean으로 등록됩니다.
첫번째 "Member memberPostDtoToMember(MemberPostDto memberPostDto);" 라는 메소드는 MemberPostDto 즉, DTO 클래스를 Member 클래스로 변환해 주는 메소드입니다.
두번째 MemberResponseDto memberToMemberResponseDto(Member member); 반대로 쉽게 말해 Member 클래스를 DTO 클래스로 다시 재변환 시켜주는 메소드입니다.
그런데 ‘인터페이스는 정의 되었긴한데 이 인터페이스의 구현 클래스는 어디 있지?’ 라고 의아해 하는 분들이 분명이 있을거라고 생각합니다.
정답은 "해당 인터페이스가 자동으로 매퍼(Mapper) 구현 클래스를 자동으로 생성해준다" 입니다.
@Component
public class MemberMapperImpl implements MemberMapper {
public MemberMapperImpl() {
}
public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
if (memberPostDto == null) {
return null;
} else {
Member member = new Member();
member.setEmail(memberPostDto.getEmail());
member.setName(memberPostDto.getName());
member.setPhone(memberPostDto.getPhone());
return member;
}
}
public Member memberPatchDtoToMember(MemberPatchDto memberPatchDto) {
if (memberPatchDto == null) {
return null;
} else {
Member member = new Member();
member.setMemberId(memberPatchDto.getMemberId());
member.setName(memberPatchDto.getName());
member.setPhone(memberPatchDto.getPhone());
return member;
}
}
public MemberResponseDto memberToMemberResponseDto(Member member) {
if (member == null) {
return null;
} else {
long memberId = 0L;
String email = null;
String name = null;
String phone = null;
memberId = member.getMemberId();
email = member.getEmail();
name = member.getName();
phone = member.getPhone();
MemberResponseDto memberResponseDto =
new MemberResponseDto(memberId, email, name, phone);
return memberResponseDto;
}
}
}
위 클래스는 제가 만든 클래스가 아니라 MapStruct가 자동으로 생성해준 MemberMapper 인터페이스의 구현 클래스입니다.
*인터페이스를 만들면 자동으로 위 클래스가 생성된다는 사실을 잊지 마세요!!!
MemberMapperImpl 클래스는 언제, 어떻게 생성될까요? IntelliJ IDE의 오른쪽 상단의 [Gradle] 탭을 클릭한 후, [프로젝트 명 > Tasks 디렉토리 > build 디렉토리 > build task]를 더블 클릭하면 MapStruct로 정의된 인터페이스의 구현 클래스가 생성됩니다.
MemberMapperImpl 클래스는 어디에 생성될까요? IntelliJ IDE의 좌측에서 [Project 탭 > 프로젝트명 > build] 디렉토리내의 MemberMapper 인터페이스가 위치한 패키지 안에 생성됩니다.
DTO 클래스와 엔티티 클래스의 분리 이유
계층별 관심사의 분리
DTO 클래스는 API 계층에서 요청 데이터를 전달 받고, 응답 데이터를 전송하는것이 주 목적인 반면에 Entity 클래스는 서비스 계층에서 데이터 액세스 계층과 연동하여 비즈니스 로직의 결과로 생성된 데이터를 다루는 것이 주 목적입니다.
코드 구성의 단순화
DTO 클래스에서 사용하는 유효성 검사 애너테이션이 Entity 클래스에서 사용이 된다면 JPA에서 사용하는 애너테이션과 뒤섞인 상태가 되어 유지보수하기 상당히 어려운 코드가 됩니다.
REST API 스펙의 독립성 확보
데이터 액세스 계층에서 전달 받은 데이터로 채워진 Entity 클래스를 클라이언트의 응답으로 그대로 전달하게되면 원치 않는 데이터까지 클라이언트에게 전송될 수 있습니다. 대표적인 예가 바로 회원의 로그인 패스워드입니다. DTO 클래스를 사용하면 회원의 로그인 패스워드 같은 정보를 클라이언트에게 노출하지 않고, 원하는 정보만 제공할 수 있습니다.
'JAVA > Spring MVC' 카테고리의 다른 글
[자바 intellij] @RequestParam? @ModelAttribute? @RequestBody는 또 뭐야?? (0) | 2022.08.14 |
---|---|
자바 Spring MVC에서의 예외 처리 @ExceptionHandler, @RestControllerAdvice (1) | 2022.06.30 |
[자바 Spring] DTO에 대해 배워보자 (1) | 2022.06.28 |
[자바 Spring] HTTP Request 헤더(Header) 정보 얻기 (0) | 2022.06.26 |
자바 Spring MVC의 개념에 대해 알아보자 (0) | 2022.06.25 |