[Spring] DTO와 ResponseEntity

김재연·2023년 3월 8일
0

수숙관

목록 보기
4/17
post-thumbnail

1. DTO

DTO란?

DTO(Data Transfer Object)는 계층 간 데이터 교환을 하기 위해 사용하는 객체로, 로직을 가지지 않는 순수한 데이터 객체(Java Beans)이다. getter/setter 메서드만을 가지며, DB에서 데이터를 얻어서 Service나 Controller 등으로 보낼 때 사용한다. (=> 엔티티를 DTO 형태로 변환한 후 사용한다.)

❓DAO(Data Access Object)란❓
DB의 데이터에 접근하기 위한 객체로, 실제로 DB에 접근해서 데이터의 삽입, 삭제, 조회, 수정 등 CRUD 기능을 수행한다. Service와 DB를 연결하는 고리 역할을 하며, Repository가 바로 DAO이다.

DTO로 Entity 생성하기

DTO를 사용해서 Review 엔티티를 생성하는 과정을 살펴보면 다음과 같다.

  1. ReviewDTO 라는 DTO 객체를 Controller에 던져준다.
  2. Controller는 이 DTO를 Service로 넘겨주고,
  3. Service에서 JPA를 사용하여 Review 객체를 저장한다.

이 과정을 코드로 보면 (필요한 부분만 뗌)

/* Review => Service에서 찐으로 객체 만들때 사용됨 */
public class Review {
    private Long id; // 자동생성
    private String body;
    private Boolean isCompleted;
    private Tag tag;
}
/* ReviewDTO => Controller와 Service의 파라미터로 사용됨 */
public class ReviewDTO {
    private Long tutoringId;
    private String body;
    private Long tagId;
}
/* Controller => Service로 DTO를 넘겨줌 */
public void createReview (@RequestBody ReviewDTO createReview){
	reviewService.createReview(createReview);
}
/* Service => DTO를 받아서 찐 엔티티를 만듦 */
public void createReview(ReviewDTO reviewDTO) {
	Optional<Tag> tag = tagRepository.findById(reviewDTO.getTagId());
	Review review = Review.builder()
                    	.body(reviewDTO.getBody())
                    	.isCompleted(false)
                    	.tag(tag.get())
                    	.build();
	reviewRepository.save(review);
}

Review 엔티티와 DTO가 비슷한듯 다르게 생겼다. 미리 작성된 API 문서에 따르면 새로운 Review 객체를 만들기 위해서는 tutoringId, body, tagId가 필요했는데, JPA로 Review 엔티티를 만들기 위해 필요한 필드(body, isCompleted, tag)와는 완전히 동일하지 않았다.

이럴때 사용할 수 있는게 바로 DTO 이다.

Controller에 파라미터로 Review를 썼다면 Review에 존재하지 않는 필드인 tutoringId을 받지 못할 것은 물론이고 1:N으로 매핑되어있는 Tag도 아이디값으로만 왔다갔다할게 아니라 Tag 자체를 다뤘어야 했을 것이다. (어휴 귀찮아라!)

그런데 ReviewDTO를 써서 실제로 받아야하는 필드만 따로 빼두니 간단하게 데이터가 전달될 수 있었던 것이다. 즉 DTO는, 쉽게 말해 내가 원하는 포맷으로 데이터를 받겠다! 라고도 할 수 있겠다.


2. ResponseEntity

반환값에 HTTP 상태코드를 같이 보낼 때 사용한다. HttpStatus, HttpHeaders, HttpBody 이 세가지 값이 필요하지만 다 넣을 필요없이 필요한 값만 넣어도 된다.

ResponseEntity<객체>는 HTTP 상태코드와 함께 이 객체에 해당하는 데이터가 같이 넘어간다는 것을 의미한다.

💡ResponseEntity<?> 이라고 쓰면 타입을 명시하지 않고 되는대로 다 받을 수 있다.

이번 프로젝트에는 기존에 ResponseCode 라는, 반환값으로 사용할 객체가 이미 만들어져있었기 때문에 얘를 같이 넘겨줄것이다.

// Entity
public class ResponseCode {
    private String code;
}

// Controller
public ResponseEntity<ResponseCode> successCode (){
        ResponseCode responseCode = ResponseCode.builder().code("SUCCESS").build();
        return new ResponseEntity<>(responseCode, HttpStatus.OK);
    }
    
public ResponseEntity<ResponseCode> failCode (){
        ResponseCode responseCode = ResponseCode.builder().code("FAIL").build();
        return new ResponseEntity<>(responseCode, HttpStatus.BAD_REQUEST);
    }

이런식으로 작성하면 ResponseCode와 함께 HTTP 상태코드가 같이 반환된다.

이미 구현되어 있는 팩토리 메소드를 사용해서 아래처럼도 보낼 수 있다.

return ResponseEntity.ok().build(); // 상태코드만 반환
return ResponseEntity.ok(response); // body까지 반환

➕ 2023.06.15 추가

3. ResponseEntity의 body에 담긴 객체 사용하기

public ResponseEntity<?> example() {
	if (...) {
    	...
    	return ResponseEntity.ok(objectA);
    } else {
    	...
    	return ResponseEntity.status(HttpStatus.NOT_FOUND)
        					 .body(objectB);
    }
}

위와 같이 함수의 반환값은 <?>으로 명시하지 않고 받고, 경우에 따라 body에 담기는 객체의 클래스가 다른 경우, 해당 객체의 값을 제대로 받아 getter, setter 등의 메소드를 사용하려면 다음과 같이 값을 받아서 앞에 해당 객체클래스를 명시해서 타입캐스팅을 해줘야한다.

ResponseEntity exampleResponse = example();
ObjectA objectA = (ObjectA) exampleResponse.getBody();
// => objectA는 A 클래스의 getter/setter 사용 가능
ObjectB objectB = (ObjectB) exampleResponse.getBody();
// => objectB는 B 클래스의 getter/setter 사용 가능
  • 그냥 쓸 때

  • 타입캐스팅을 하고 쓸 때


Reference

[스프링/Spring] DTO는 왜 써야 하나?
[Spring] DAO와 DTO의 차이 그리고 VO
ResponseEntity란 - 개념, 구조, 사용법, 사용하는 이유

profile
일기장같은 공부기록📝

0개의 댓글