[kotlog] 코프링으로 블로그 만들기 - 2 (도메인 생성, BaseEntity)

dustle·2025년 6월 11일
1

kotlog

목록 보기
2/6

ERD 를 만들고 도메인을 생성하였습니다.

매우 무난한 블로그 ERD 입니다.


1. 기본적인 도메인 생성

우선 ERD를 바탕으로 User 엔티티를 생성했습니다.
패키지는 도메인별로 나눴고, JPA 매핑을 위해 어노테이션도 붙였습니다.

//User Entity
@Entity
@Table(name = "users")
class User(

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long,

    @Column(name = "user_name", nullable = false)
    val username: String,

    @Column(name = "password", nullable = false)
    val password: String,

    @Column(name = "email", nullable = false)
    val email: String,

    @Column(name = "nickname", nullable = false)
    val nickname: String,

    @Column(name = "created_at", nullable = false)
    val createdAt: ZonedDateTime,

    @Column(name = "updated_at", nullable = false)
    val updatedAt: ZonedDateTime
)

여러 필드 중 id, createdAt, updatedAt 는 앞으로 다른 엔티티에서도 반복되게 됩니다.

그래서 이 공통 필드를 하나의 추상 클래스에 모으기로 했습니다.

2. BaseEntity 추출

JPA에서 자주 사용되는 어노테이션 중 하나인 @MappedSuperclass 를 활용해 추상 클래스를 공통 부모 클래스로 사용할 수 있습니다.
이렇게 하면 DB에 테이블은 생성되지 않지만, 하위 클래스에서 해당 필드를 자신의 것처럼 사용할 수 있습니다.

@MappedSuperclass
abstract class BaseEntity {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private val _id: Long? = null
    val id: Long
        get() = _id!!

    @Column(name = "created_at", nullable = false)
    var createdAt: ZonedDateTime = ZonedDateTime.now()

    @Column(name = "updated_at", nullable = false)
    var updatedAt: ZonedDateTime = ZonedDateTime.now()
}

처음에는 JPA 에서 값을 주입하기 위해 val id: Long? = null 로 단순하게 nullable 하게 선언했다가 문제가 생겼습니다.

  • val은 setter가 없지만 nullable 이라서 사용할 때마다 null 체크 or !! 필요함
  • var로 하면 setter가 생겨버려서 외부에서 마음대로 바꿀 수 있게 됨

그래서 backing field 라는 걸 사용한다고 합니다.

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private val _id: Long? = null
val id: Long
    get() = _id!!
  • _id 는 JPA가 사용할 nullable 필드
  • id 는 get() = _id!! 를 사용하여 외부에서 non-nullable 로 접근할 수 있는 읽기 전용 필드

가 되어서 id 를 깔끔하고 안전하게 노출할 수 있습니다.

3. Entity 에서 BaseEntity 상속

@Entity
@Table(name = "users")
class User(

    @Column(name = "user_name", nullable = false)
    val username: String,

    @Column(name = "password", nullable = false)
    var password: String,

    @Column(name = "email", nullable = false)
    var email: String,

    @Column(name = "nickname", nullable = false)
    var nickname: String
) : BaseEntity()

불필요하게 반복되던 필드가 사라지고 코드가 간결해졌습니다.

4. ZonedDateTime

createdAt, updatedAt 타입으로 ZonedDateTime을 사용한 이유는 다음과 같습니다.

  • java.util.Date는 불변이 아님
  • LocalDateTime은 시간대 정보가 없음
  • ZonedDateTime은 타임존(Asia/Seoul 등) 정보를 명확히 포함함

실제 운영 환경에서는 서버, DB, 프론트엔드가 각기 다른 시간대를 가질 수 있기 때문에,
명시적인 시간대 정보를 가지는 ZonedDateTime이 더 안전하다고 합니다.


다음 글에서는 코드 스타일 실수를 줄이기 위해 ktlint 같은 Lint 도구를 적용해보고,
엔티티의 updatedAt 값을 자동으로 갱신하기 위한 @PreUpdate 설정도 함께 적용해 볼 생각입니다.

0개의 댓글