계층연동 : API계층과 서비스 계층을 연동한다는 의미는 API계층에서 구현한 Controller클래스가 서비스 계층의 Service클래스와 메서드 호출을 통해 상호작용한다는 것을 의미
// 회원관리 앱의 service메서드의 틀
import java.util.List;
public class MemberService {
public Member createMember(Member member) {
return null;
}
public Member updateMember(Member member) {
return null;
}
public Member findMember(long memberId) {
return null;
}
public List<Member> findMembers() {
return null;
}
public void deleteMember(long memberId) {
}
API계층에서는 요청데이트(RequestBody)를 전달받을떄 DTO클래스를 사용했다
즉, DTO가 API계층에서 클라이언트의 RequestBody를 전달받고 클라이언트에게 되돌려줄 응답데이터를 담는 역할을 한다면,
Member클래스는 API계층에서 전달받은 요청데이터를 기반으로 서비스 계층에서 비즈니스로직을 처리하기 위해 필요한 데이터를 전달받은 후 비즈니스 로직을 처리하고, 그 결과 값을 다시 API계층으로 리턴해주는 역할을 수행
Member클래스 처럼 1) 서비스 계층에서 데이터 엑세스 계층과 연동하면서 2) 비즈니스로직을 처리하기 위해 필요한 데이터를 담는 역할을 하는 클래스를 엔티티(Entity)클래스라 부름
//Member 클래스 구현 ( API계층에서 요청받는 Controller클래스에서필드를 그대로 사용)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
private long memberId;
private String email;
private String name;
private String phone;
}
private final MemberService memberservice; ( 변수 선언)
// 생성자 DI를 통해 객체사용을 가능케한다
Public MemberController(MemberService memberservice){
this.memberservice = memberservice ;}
그러므로 서비스 클래스에 @Service를 추가해 Spring Bean등록!
@RestController
@RequestMapping("/v2/members")
@Validated
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
// (2)
Member member = new Member();
member.setEmail(memberDto.getEmail());
member.setName(memberDto.getName());
member.setPhone(memberDto.getPhone());
// (3)
Member response = memberService.createMember(member);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(
@PathVariable("member-id") @Positive long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// (4)
Member member = new Member();
member.setMemberId(memberPatchDto.getMemberId());
member.setName(memberPatchDto.getName());
member.setPhone(memberPatchDto.getPhone());
// (5)
Member response = memberService.updateMember(member);
return new ResponseEntity<>(response, HttpStatus.OK);
}
@GetMapping("/{member-id}")
public ResponseEntity getMember(
@PathVariable("member-id") @Positive long memberId) {
// (6)
Member response = memberService.findMember(memberId);
return new ResponseEntity<>(response, HttpStatus.OK);
}
현재 코드의 문제점은 MemberController에서는 핸들러 메서드가 DTO 클래스를 엔티티(Entity) 객체로 변환하는 작업까지 수행
즉, Controller가 너무 많은 역할 담당
매퍼(Mapper)란 : DTO클래스와 Entity클래스 변환을 하는것은 Mapper
@Component // Spring bean으로 등록하기 위한 Component
public class MemberMapper {
// MemberPostDto를 Member로 변환하는 코드
public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
return new Member(0L,
memberPostDto.getEmail(),
memberPostDto.getName(),
memberPostDto.getPhone());
}
}
이렇게 해서 Dto클래스를 Entitiy 클래스인 Member로 변환하는 과정
But ! 개발자가 클래스마다 일일이 알맞는 Mapper클래스를 만드는것은 비효율적이다!
하지만 Spring에서는 이 매퍼클래스를 자동으로 만들어주는 MapStruct가 있다!
MapStruct : 매퍼 구현 클래스를 자동으로 생성해주는 '코드 자동 생성기'
//엔티티클래스를 다시 Dto로 보내주는 클래스
@Getter
@AllArgsConstructor
public class MemberResponseDto {
private long memberId;
private String email;
private String name;
private String phone;
}
// 의존라이브러리에 mapstruct 설정추가
dependencies {
...
...
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
MapStruct 기반의 매퍼(Mapper) 인터페이스 정의
@Mapper(componentModel = "spring") public interface MemberMapper { Member memberPostDtoToMember(MemberPostDto memberPostDto); Member memberPatchDtoToMember(MemberPatchDto memberPatchDto); MemberResponseDto memberToMemberResponseDto(Member member); } // (MemberpostDto ~ ) -> 데이터 시작점 // Member -> 반환점
컨트롤러 클래스위에 import문으로 mapstruct위치로 알려주면 바로 mapper클래스 사용가능
import com.app.member.mapstruct.mapper.MemberMapper; // 패키지 변경 하면됨
@RestController
@RequestMapping("/application/members")
@Validated
public class MemberController {
private final MemberService memberService;
private final MemberMapper mapper; //매퍼 사용하기 위한 선언
// MemberMapper DI
public MemberController(MemberService memberService, MemberMapper mapper) {
this.memberService = memberService;
this.mapper = mapper; // 매퍼사용하기 위한 DI
}
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
// 매퍼를 이용해서 MemberPostDto를 Member로 변환
Member member = mapper.memberPostDtoToMember(memberDto);
Member response = memberService.createMember(member);
// 매퍼를 이용해서 Member를 MemberResponseDto로 변환
return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),
HttpStatus.CREATED);
}
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(
@PathVariable("member-id") @Positive long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// 매퍼를 이용해서 MemberPatchDto를 Member로 변환
Member response =
memberService.updateMember(mapper.memberPatchDtoToMember(memberPatchDto));
// 매퍼를 이용해서 Member를 MemberResponseDto로 변환
return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),
HttpStatus.OK);
}
@AllargsConstructor : 클래스에 추가된 모든멤버변수를 파라미터로 갖는 Member생성자 자동 생성
@NoargsConstructor : 파라미터 없는 기본생성자 자동 생성
@Service : 컨트롤러 클래스에서 서비스 클래스 사용하기 위해서는 생성자 주입을통해 사용이 가능케 하는데 스프링이 객체를 주입해주기 위해서는 BEAN등록이 필수!
Bean 등록을 하기 위해서는 서비스 클래스에 @Service 애너테이션을 추가