Spring Boot (+ RESTful)

RESTful 웹 서비스 구축 - @PrePersist / DTO 구분 (Entity, Payload, View)

wy-family 2024. 12. 24. 17:29

REST 프로젝트를 MVCS 구조, 3-tier 구조로 만들어보고자 한다.

 


createdAt 같은 경우에는, 우리가 직접 값을 입력하는게 아니라 DB에 데이터가 insert 가 될 때, 날짜는 자동으로 입력이 되도록 하기 위해서 @PrePersist 라는 어노테이션을 붙이면 Book 이라는 객체가 만들어져서 데이터가 DB table 에 들어갈 때 미리 @PrePersist 라는 어노테이션 부분이 실행이 된다. onCreate() 메소드가 실행이 될 것이라, 날짜를 우리가 입력하는게 시스템에서 직접 입력이 될 수 있도록 해주면 된다. protected 라고 했지만, public 이라고 해도 문제는 없다.

1. @PrePersist의 의미

  • PrePersist"저장 전에" 라는 뜻을 가진 영어 단어입니다.
    • Pre: "이전에" 또는 "미리"
    • Persist: "지속하다", "저장하다", (여기서는 데이터베이스에 저장된다는 의미)

@PrePersist는 **JPA (Java Persistence API)**에서 제공하는 라이프사이클 콜백 어노테이션 중 하나입니다.


2. @PrePersist 어노테이션의 기능

  • 이 어노테이션이 붙은 메소드는 Entity가 데이터베이스에 저장되기 직전에 실행됩니다.
  • 즉, INSERT 쿼리가 실행되기 전 단계에서 특정 동작을 정의하고 실행할 수 있습니다.

예시 설명:

  • onCreate() 메소드 안에서 createdAt 필드에 LocalDateTime.now() 값을 할당합니다.
  • 이는 Book 객체가 새로 저장될 때마다 현재 시스템 시간을 자동으로 createdAt 필드에 입력하는 역할을 합니다.
    따라서 createdAt 값을 사용자가 직접 입력하지 않아도 됩니다.

3. protected vs public 접근 제어자

  • protectedpublic 둘 다 JPA에서 실행되기에는 문제가 없습니다.
  • 왜냐하면 JPA는 접근 제어자에 상관없이 해당 메소드를 호출할 수 있기 때문입니다.
    • JPA는 리플렉션(reflection)을 사용하여 클래스와 메소드에 접근합니다.
      즉, 접근 제어자(public, protected, private)에 관계없이 JPA 내부적으로 메소드를 호출할 수 있습니다.

접근 제어자의 차이:

  1. protected
    • 같은 패키지 내의 클래스나 하위 클래스에서만 접근이 가능합니다.
    • 외부에서 직접적으로 접근할 필요가 없는 메소드는 protected로 선언하는 것이 좋습니다.
  2. public
    • 어디서든 접근이 가능합니다.
    • 특별히 제한할 이유가 없다면 public을 사용해도 문제는 없습니다.

결론

  • @PrePersist는 엔티티가 데이터베이스에 저장되기 직전에 실행되는 메소드를 지정하는 어노테이션입니다.
  • onCreate() 메소드를 사용하면 createdAt 필드를 시스템의 현재 시간으로 자동 설정할 수 있습니다.
  • protected 또는 public 중 어떤 것을 사용하더라도 JPA는 메소드를 실행할 수 있으므로 기능상 차이는 없습니다.
    하지만 보안상 외부 접근을 막기 위해 protected를 권장하는 경우가 많습니다.

  1. DTO (Data Transfer Object)
    • 데이터를 전송하거나 보관하기 위해 사용하는 객체.
    • 목적: 데이터를 다른 계층 (Controller ↔ Service ↔ DB) 간에 이동시키기 위해 사용된다.
  2. 왜 엔티티(Entity)를 DTO로 쓰면 안 되는가?
    • 엔티티는 테이블과 직접 연결된 클래스로, 테이블 구조와 관계를 반영하기 위해 사용된다.
    • 데이터를 입력하거나 수정할 때 사용자가 제공하지 않는 필드(id, createdAt 등)도 따라다니기 때문에 불필요한 리소스 낭비가 발생한다.
    • 만약 엔티티를 변경하면 테이블 구조에 영향을 미칠 수 있으므로 DTO를 분리하는 것이 바람직하다.
  3. Payload DTO (BookPayloadDTO)
    • 목적: 클라이언트로부터 데이터를 받을 때 사용된다.
    • 특징: 불필요한 필드(id, createdAt)가 없고, 입력받아야 하는 필드만 정의한다.
    • 유효성 검사를 위해 @NotBlank 같은 어노테이션을 추가한다.
    • 장점:
      • 테이블(Entity)에 영향을 주지 않으면서 필요한 데이터만 수집할 수 있다.
      • 필수값 검증과 같은 로직을 추가하기 쉽다.
    • 단점:
      • 클래스가 많아져서 프로젝트 구조가 복잡해질 수 있다.
  4. View DTO (BookViewDTO)
    • 목적: 클라이언트 화면(View)에 데이터를 전달할 때 사용된다.
    • 특징: 화면에 필요한 필드만 담아서 반환한다.
      • 예를 들어, id와 createdAt 같은 필드는 화면에 뿌릴 때 유용할 수 있다.
    • 장점:
      • 뷰에 맞춰 가공된 데이터를 제공할 수 있다.
      • 필요에 따라 데이터를 자유롭게 추가하거나 제거할 수 있다.
    • 단점:
      • PayloadDTO와 마찬가지로 여러 클래스가 생겨서 복잡도가 증가한다.

추가 질문

1. DTO가 무엇인가?

DTO는 Data Transfer Object의 약자로, 계층 간 데이터를 전송하거나 전달하기 위해 사용하는 객체입니다.
주요 용도:

  • 데이터를 보관하거나 이동할 때 사용합니다.
  • Controller → Service → Repository 간에 데이터를 안전하게 주고받는 역할을 합니다.

2. PayloadDTO를 만들면 어떤 장점이 있고, 단점은 없는가?

  • 장점:
    1. 불필요한 필드 배제: 입력받을 데이터만 필드로 정의하므로 리소스 낭비가 줄어듭니다.
    2. 유효성 검사 가능: @NotBlank 같은 어노테이션을 통해 값 검증이 가능합니다.
    3. Entity 보호: Entity는 테이블과 연결되므로, 변경 시 DB에 영향을 줄 수 있지만, PayloadDTO는 안전합니다.
  • 단점:
    1. 클래스가 많아짐: 입력, 출력, 뷰를 구분하면 코드의 양이 많아져서 복잡도가 증가합니다.
    2. 유지보수 부담: 필드나 요구사항이 자주 변경되면 여러 DTO를 모두 수정해야 합니다.

3. ViewDTO를 만들면 어떤 장점이 있고, 단점은 없는가?

  • 장점:
    1. 가공된 데이터 제공: 화면(View)에 필요한 필드만 포함할 수 있습니다.
    2. 확장성: 화면에 추가적으로 필요한 데이터를 자유롭게 추가하거나 제거할 수 있습니다.
    3. Entity와 독립: View에 맞춘 데이터를 반환하기 때문에 Entity 변경과 무관합니다.
  • 단점:
    1. 복잡성 증가: ViewDTO까지 분리하면 클래스의 수가 늘어나 프로젝트가 복잡해 보일 수 있습니다.
    2. 중복 필드: 같은 필드가 여러 DTO에 중복해서 정의될 수 있습니다.

4. @NotBlank vs @Schema(requiredMode = Schema.RequiredMode.REQUIRED)

  • @NotBlank
    • 기능:
      • 유효성 검사를 위해 사용합니다.
      • 필드가 null이 아니고, 빈 문자열이 아닌지 검사합니다.
    • 위치:
      • 서버 백엔드에서 실행되며, Jakarta Bean Validation API에 속합니다.
    • 용도:
      • 값이 필수로 입력되어야 하는지를 검증할 때 사용합니다.
  • @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
    • 기능:
      • Swagger 문서에 해당 필드가 필수값임을 명시하는 데 사용됩니다.
    • 위치:
      • API 문서 (OpenAPI/Swagger)에 반영되며 프론트엔드 개발자에게 필수 여부를 알려줍니다.
    • 용도:
      • Swagger 문서를 통해 API를 호출할 때 필요한 필드임을 강조합니다.

결론:

  • @NotBlank는 실제 값 검증을 수행합니다.
  • @Schema는 Swagger 문서에 필수 필드로 나타내기 위한 설명용 어노테이션입니다.
  • 두 어노테이션은 함께 사용하면 유효성 검사와 문서화를 동시에 처리할 수 있습니다.

요약

  • PayloadDTO: 데이터를 받을 때 필요한 DTO → 유효성 검사 용도.
  • ViewDTO: 데이터를 보낼 때 화면(View)에 맞춘 DTO → 확장성과 유연성 제공.
  • @NotBlank: 값이 필수로 입력되어야 함을 검증.
  • @Schema: Swagger 문서에서 필드가 필수임을 표시.
DTO 를 만들것이다. Book 은 @Entity 가 붙어서 Table 로 만들어지는 DTO 이다. Book 은 Table 하고 Relation, 관계를 맺고 있기 때문에 Book 엔티티 클래스를 우리가 실제 프로그램을 짤 때 DTO 개념으로 사용하기에는 곤란하다. 사용자가 데이터를 입력하게 되면은 그 데이터를 서버에서 받아가지고 서버에서 그걸 담기 위해서 (보관하기 위해서) 사용하는 클래스가 일반적으로 DTO 인데, 엔티티 클래스 (Table 로 만들어지는 클래스) 를 DTO 의 용도로 쓰기에는 조금 부담이 된다. 왜그런가하면 책(Book) 데이터를 입력한다고 했을 때에는 사용자가 일반적으 제목(subject), 가격(price), 저자(author), 쪽수(page) 정보만 입력하게 된다. 그 입력된 정보를 받을 때 Book 클래스를 이용하게 되면은 id, createdAt 같은 경우에는 입력을 하지 않기 때문에 id, createdAt 은 전혀 사용을 하지 않는다. 하지만 Book 클래스를 사용하게 되면은 입력되지는 않지만 id 와 createdAt 도 계속 따라다니기 때문에 좀 불필요하다. 그래서, Entity 가 아닌, Payload 라는 개념의 DTO 를 하나 만들 수가 있다.
Payload (BookPayloadDTO) 는 언제 써먹는 것이냐면 클라이언트로부터 넘어오는 데이터를 받는 용도. 그러면 BookPayloadDTO 라는 클래스는 id는 필요가 없다. createdAt 도 필요가 없다. 데이터를 받는 용도로만 사용하기 때문에 필요한 것들만 만들어놓으면 된다. subject, price, author, page 만 필요하다. 만약, 추가로 어떤 값을 받으려고 한다면 Payload 를 수정할 수 있다. 근데 만약 Entity 클래스를 데이터를 받는 용도로 사용하고 있었다면, 그래서 추가로 어떤 값을 받으려고 한다면, 또는 어떤 값을 뺀다고 한다면, Entity 이기 때문에 table 에 영향을 미쳐버린다. 그래서 Entity (Book) 는 그냥 table 대용으로 놔두고 Payload (BookPayloadDTO) 라는 데이터를 받는 용도와 View (BookViewDTO) 라는 데이터를 뷰에다가 넘길 때 쓰는 용도로 구분지어놓을 수가 있겠다.
View (BookViewDTO) 는 뷰와 관련되어 있는 용도이다. 화면에 뭔가 데이터를 뿌리기 위해서는 데이터를 담아가지고 뷰에다가 넘겨야한다. 그 때는, Payload를 쓸 수도 있지만, 뷰에다가 뿌리는 데이터에는 id 와 createdAt 의 날짜 정보가 같이 들어있는 DTO가 필요할 수 있다. 그래서 뷰와 관련있는 용도로 활용하기 위해 BookViewDTO 라고 하나 따로 만들 수가 있겠다. 여기에는 특별한 어노테이션이 필요하지 않고, 그냥 데이터를 담아가지고 뷰에다가 넘길 때에 사용하면 되겠다. 가공을 하거나 늘리거나 빼거나 자유자재로 하기에 용이하다.
하나의 클래스만 정의해도 되는데, 여러개를 정의하다보니 복잡하게 느껴질 수 있다. 나중에 확장성이나 유지보수의 측면에서는 구분지어서 사용하는 것이 바람직할 수 있다. 그리고 Payload 의 경우에는 유효성 검사를 해볼 수가 있다. 왜냐면 데이터를 받는 용도이기 때문에 값이 들어오지 않으면 예외를 발생시킬 수가 있다. @NotBlank 라는 어노테이션을 적으면 값이 꼭 입력이 되어야 한다는 의미이다.

<추가 질문>
DTO 가 뭐였지?
이렇게 하는 이유가 뭔지? PayloadDTO 를 만들면 어떤 장점이 있는지, 단점은 없는지.
이렇게 하는 이유가 뭔지? ViewDTO 를 만들면 어떤 장점이 있는지, 단점은 없는지.
@NotBlank 와 @Schema(requiredMode = Schema.RequiredMode.REQUIRED) 의 차이가 무엇인지.

DTO 를 구분지어서 만들어보았고,

Service 와 Repository 레이어를 만들어볼 것이다.

 

레포지토리 인터페이스 interface

 

 

- @Autowired 로 레퍼지토리를 주입받고

- 저장, 수정(업데이트), 전체 리스트 가져오기, 특정 레코드 1개 가져오기, 삭제 서비스 만들었다.

 

Controller는 다음 게시물에서..