ERD 를 만들고 도메인을 생성하였습니다.
매우 무난한 블로그 ERD 입니다.
우선 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
는 앞으로 다른 엔티티에서도 반복되게 됩니다.
그래서 이 공통 필드를 하나의 추상 클래스에 모으기로 했습니다.
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 하게 선언했다가 문제가 생겼습니다.
그래서 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
를 깔끔하고 안전하게 노출할 수 있습니다.
@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()
불필요하게 반복되던 필드가 사라지고 코드가 간결해졌습니다.
createdAt, updatedAt 타입으로 ZonedDateTime을 사용한 이유는 다음과 같습니다.
실제 운영 환경에서는 서버, DB, 프론트엔드가 각기 다른 시간대를 가질 수 있기 때문에,
명시적인 시간대 정보를 가지는 ZonedDateTime이 더 안전하다고 합니다.
다음 글에서는 코드 스타일 실수를 줄이기 위해 ktlint 같은 Lint 도구를 적용해보고,
엔티티의 updatedAt 값을 자동으로 갱신하기 위한 @PreUpdate 설정도 함께 적용해 볼 생각입니다.