차근차근 Modern Spring Boot 3 기초 (4) Persistent Entity 만들기(JPA Entity)

Merge SimpsonMerge Simpson
3 min read

Entity, DTO 용어의 제한

Entity는 범용적인 용어입니다. DTO 또한 데이터 전달에 사용되면 모두 DTO라고 할 수 있죠. 하지만 이렇게 넓은 의미로 사용되면, 작업 스타일을 정할 때 방해가 될 수 있습니다.

우리는 다음처럼 entity와 DTO의 의미를 제한해 보겠습니다.

  • Entity: 구체적으로 JPA Entity를 뜻하는 것으로 하겠습니다. 이렇게 하면 결국 테이블에 그대로 대응하는 데이터가 됩니다.

  • DTO: 오직 사용자(또는 다른 서버 등)와 주고 받는, 즉 외부와 교류하는 데이터 양식을 DTO라고 부르겠습니다.

JPA Entity 만들기

우리는 Flyway를 통해 DDL을 실행합니다. 테이블은 그렇게 만들죠.

바꿔서 말하자면, 테이블에 대응하는 JPA Entity도 DDL에 대응하게 만들면 됩니다.

Enum을 통한 상태 목록 관리

Enum은 C언어 시절에도 선택지 목록을 관리할 때 유용한 구조였습니다. 자바에서도 enum 클래스가 선택지 구조에서 유용하게 활용되며, 예를 들어 무언가의 상태(status)를 몇 가지 중 한 가지로 표현할 때 사용할 수 있습니다.

단, enum으로 관리하는 경우, 나중에 목록을 수정할 때마다 프로그램을 다시 배포해야 합니다. 간단히 구분해 보자면 다음과 같습니다. (절대적인 것은 아니며, 항상 상황에 따라 다를 수 있습니다.)

  • 수정할 필요가 (거의) 없는 선택지 목록 관리: enum으로 관리 (재배포에 의존)

  • 수정할 경우가 때때로 생기는 선택지 목록 관리: 데이터베이스를 통해 동적으로 관리.

개발자가 설계할 적에, 예상되는 수정의 빈도만으로 결정했을 때에는 운영 과정에서 생각보다 동적으로 관리해야 했던 것들을 enum으로 작성하였던 경우가 생길 수 있습니다. 따라서 단지 주관적으로 생각한 빈도로 선택하는 것은 아닙니다.

Enum Account Status

우리는 앞서 DDL 작성 시 status라고 하는 컬럼을 작성하였습니다. 타입은 문자열이었습니다. 이것을 enum으로 관리해 보겠습니다. 다음처럼 작성하면 이제 자바 타입으로 사용할 수 있습니다.

  • Package: com.example.demo.auth.domain
public enum AccountStatus {
    /** 가입 대기 */
    PENDING,
    /** 활성화 */
    ACTIVE,
    /** 보호됨(비밀번호를 연속으로 틀리는 등) */
    PROTECTED,
    /** 블락 처리 */
    SUSPENDED,
    /** 휴면 계정 */
    SLEPT,
    /** 삭제된 계정 */
    REMOVED
}

Account Entity 클래스 작성

엔티티 클래스는 우리가 데이터베이스의 테이블을 다룰 때 편리하게 다룰 수 있도록, 테이블에 매핑되는 클래스를 선언한 것입니다. 엔티티는 범용적인 표현이기 때문에, 데이터베이스와 무관한 엔티티 개념도 사용되지만, 우리는 분명히 데이터베이스 테이블을 다루기 위한 JPA 엔티티를 언급하고 있습니다.

엔티티 클래스를 작성하기 위해서는, 앞서 만든 DDL 파일을 보면서 테이블의 컬럼을 알맞게 옮겨 주면 됩니다. 우리는 Flyway를 통해 DDL 관리를 하고 있기 때문에 @Column 같은 애노테이션을 세세하게 작성하지 않아도 됩니다.

DDL Auto의 속성 값을 none으로 했거나 따로 설정하지 않았다면 아무 문제가 없으며, validate 등 세세한 체크를 켜 두었다면 Flyway에 적용된 제약조건을 엔티티에도 반영해야 합니다.

import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.Instant;
import java.util.UUID;
import java.util.function.Supplier;

@Entity
@Table(
        name = "account"
)
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    @Id
    @GeneratedValue(generator = "uuid2")
    private UUID id;

    private String username;
    private String password;
    private String nickname;
    @Enumerated(EnumType.STRING)
    private AccountStatus status;
    private Instant createdAt;
    private Instant updatedAt;

    // setter 삼가기.
    // 수정할 부분만 모아서 여러 update 메서드를 따로 만드는 것.
}
  • id는 자동으로 생성됩니다.

  • 중요한 것은 우리는 enum 타입을 사용할 때, 가급적 EnumType.String으로 사용합니다. 작성 시 많이 누락하는 부분이니, 잘 체크해 두세요.

  • 또 타임존, 타임 오프셋 등에 구애받지 않는 시스템을 구성하는 것이 좋습니다.

    • GMT, UTC 기준으로 +00:00에 맞추어 타임스탬프를 사용합니다. (유닉스 타임스탬프)

    • 타임존과 그 존의 오프셋은 '클라이언트'가 결정할 영역입니다.

이러한 이유로 시간에는 Instant 타입(Java 8 이상)을 사용하고, DB에서는 timestamp를 사용하는 것이 일반적인 선택이 되고 있습니다. 여러분이 아는 글로벌 테크기업들 중에도 그런 경우가 많죠!

그 외에도 getter는 허용하면서 setter는 잘 허용하지 않는 것이, 널리 쓰이고 있는 기초 아키텍처에서 함께 선택되는 전략입니다. 이는 엔티티 객체의 변화를 방지하기 위한 조치입니다. (일부 아키텍처에서는 변화를 public으로 허용할 수도 있습니다. 몇몇 리스크를 제거한 상태에서 택할 수 있는 전략입니다.)

그리고 위 엔티티 클래스에는 편의상 @Builder를 추가해 두었습니다. 자바는 모던한 언어들에 비해서 생성자나 메서드의 파라미터를 다루는 것이 조금 불편한데, 롬복(lombok)의 빌더는 빌더 패턴을 통해 이를 보완하고 있습니다.


< Prev

Flyway를 통한 DDL 관리

Next >

JPA Entity를 사용하는 JPA Repository

0
Subscribe to my newsletter

Read articles from Merge Simpson directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Merge Simpson
Merge Simpson

Hello, I am Korean. Welcome, visitor. You are very cool. 안녕하세요, 저는 한국어입니다. 방문자여 환영한다. 당신은 매우 시원해.