엔티티 만들기

🏁 학습할 내용
- DB 용어 정리
- 엔티티관련 어노테이션
- @Entity
- @id
- @GeneratedValue
- @Column
📝DB 용어 정리
DB를 사용하기위해서 다양한 개념들이 필요한데, 그 중 정말 기본적인 내용을
이번 포스팅에서 간단히 살펴보자.
먼저 DB는 행과 열로 구성된 일종의 표 형태다.
| 열1 | Attribute1 | 열3 |
| 행1 (레코드1) | 필드1 | |
| 행2 |
🏌️필드와 Attribute(특성, 속성)
필드는 표에서 열(column)에 해당하는 가장 작은 단위의 데이터를 의미한다.
필드는 엔티티의 속성을 표현한다.
엔티티를 설명하는 특성을 Attribute라고 한다.
엔티티마다 다를 수 있고, 이를 통해 엔티티를 구별할 수 있다.
❓필드와 Attribute의 차이는?
Attribute는 설계 상의 속성이고, Field는 실제 데이터가 저장되는 칼에 해당한다.
둘다 문맥 상, 열(Column)을 가르킨다.
ID = 속성, ID = 12345 = 필드 (엔티티의 속성을 표현)
💿레코드 또는 튜플
레코드는 필드의 집합으로, 필드의 집합은 가로(행)으로 묶여진다.
즉 고객 테이블일 경우, 고객 당, 각각의 레코드가 있는 형태
📖 테이블
연관된 레코드들의 집합
🙋엔티티
드디어 오늘의 주인공인 엔티티다.
엔티티는 현실 세계에 존재하는 것을 데이터베이스에 표현하기위한 추상적인 개념
학생, 주문, 상품등 데이터로 관리할 필요가 있는 객체
🔹엔티티 관련 어노테이션
@Entity
JPA에서 "이 클래스는 데이터베이스 테이블과 매핑되는 엔티티"라는 것을 선언
@Entity를 사용할 떄 주의사항이 있는데 바로 Proxy 패턴을 쓰기위해서 지켜야하는 내용이다.
프록시 패턴은, 프록시라는 객체를 앞단에 놓고 다양한 역할 수행을 맡기는 패턴이다.
JPA는 객체를 복원할 때, 리플렉션을 사용하기 떄문에, 기본생성자(파리미터가 없는 생성자)가 필수로 필요
Hibernate는 lazy 로딩 등을 위해 엔티티에 프록시를 만들어야 하는데
프록시는 상속을 통해 만들어지므로 클래스가 open이 되야함
정리하면 다음과 같다.
- 기본 생성자(파라미터가 없는)
- final과 함께 사용 불가
@Id
지정된 속성을 id로 지정한다. 여기서 id로 지정한다는 의미는 다음 특징을 같는다.
- 기본키로 지정
- 고유한 값이여야하므로, 중복되면 안됨
@GeneratedValue
데이터를 저장할 때, 지정된 속성은 값을 별도로 입력하지 않아도 자동으로 1씩 증가되여 저장된다.
여기다, 고유하게 생성하고 싶으면, 아래와 같이 strategy값을 다음과 같이 설정하면된다.
| 전략 | 특징 | 대표DB |
| IDENTITY | DB auto_increment | MySQL, MariaDB |
| SEQUENCE | 시퀀스 오브젝트 사용, 성능 좋음 | Oracle, PostgreSQL |
| TABLE | 별도 테이블로 키 관리 (느림) | 모든 DB |
| AUTO | 방언 따라 자동 선택 | 전 DB |
@Column
위에서 배운 개념에 따르면, 테이블의 열 이름과 일치하는데, 열의 세부 설정을 위해서 사용한다.
@Column을 별도로 달지 않아도, @Entitiy가 붙어잇는 객체의 필드는 자동으로 인식하지만
확실하게 붙여주는게 좋을 듯 하다.
또한 카멜케이스 형식으로 작성되면 자동으로 snake방식으로 데이터베이스 테이블의 열로 기록된다.
createDate -> create_date
✅실습
🔨 설치 및 설정
plugins { // 빌드 도구에게 어떤 기능을 사용할지 선언하는 곳
kotlin("plugin.jpa") version "1.8.22" // JPA + Kotlin 사용할 때는 필수 플러그인
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
...
}
allOpen {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.Embeddable")
annotation("jakarta.persistence.MappedSuperclass")
}
- allOpen
- Kotlin 클래스는 기본적으로 final인데 Hibernate는 프록시 생성을 위해 open class가 필요함.
- 그래서 @Entity, @Embeddable, @MappedSuperclass가 붙은 클래스는 자동으로 open 처리해줌.
❓질문 엔티티 만들기
먼저 가장 간단하게, 게시판 질문 형태의 초안을 만들면 아래처럼 만들 수 있을 것 같다.
여기서 흥미로운 점은 val 선언했다는 점인데, 이 뜻은 setter가 없다는 의미
왜냐하면 데이터베이스와 바로 연결되는 데이터로 쓰이기 때문에, 자유롭게 변경할 수 있는 setter는
허용하지 않는게 안전하다고 판단되는 것 같다.
즉, 엔티티는 생성자에 의해서만 생성되고, 별도의 변경은
추가적인 메서드를 통해서 하는 형태를 지향한다고한다.
@Entity
class Question(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int,
@Column(length = 200)
val subject: String,
@Column(columnDefinition = "Text")
val content: String,
val createDate: LocalDate,
@OneToMany(mappedBy = "question", cascade = [CascadeType.REMOVE])
val answerList: List<Answer>
)
- @OneToMany
- 1:N을 의미, 1개의 질문에 여러개의 답변이 달릴 수 있음을 의미
- mappedBy: Answer객체의 question이라는 필드에 Question의 PK가 FK로 사용되고 있음을 의미
- cascade: 부모 엔티티에 대한 작업이 자식 엔티티에도 전파되는 설정
- CascadeType.Remove: 부모가 제거 시, 자식도 제거됨 (질문이 제거되면 그에 달린 답변도 제거)
🙋답변 엔티티 만들기
@Entity
class Answer(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int,
val createDate: LocalDate,
@ManyToOne // Many(Answer) One(Question)
val question: Question
)
- @ManyToOne
- N:1 관계를 나타냄, 1개의 질문에 N개의 답변이 달릴 수 있음
🤝 관계도

먼저 테이블이 만들어진게 확인된다.
여기서 답변 테이블에 QUESTION_ID를 주목해보자.
바로 이게 위에서 명시한, @OneToMany에서 mappedBy로 연결한 결과다.
QUESTION의 ID(PrimaryKey) 값을 외래키로 사용하여, 외부에서 어떤 질문에 대한 답변인지 확인할 수 있다.
정리하면 외래키를 갖고 있는 곳이 Many(자식)가 되고,
자신의 PK를 외래키로 넘겨준 쪽이 One(부모)가 됨