7 min to read
[스프링부트 시리즈7] 엔티티로 테이블 매핑하기
JPA를 사용하기 위한 엔티티 이해하기.
엔티티로 테이블 매핑하기
데이터베이스 구성요소 살펴보기
기본적으로 2차원 표 형태의 데이터베이스가 관리된다. 표 형태의 데이터 저장공간을 테이블이라 하고 테이블은 가로줄과 세로줄로 구성되어 있으며 가로줄을 행(Row)라고 하고 세로줄을 열(column)이라 한다.
여기서 중요한 것은 기본키(Primary Key)라고 하는데 기본키는 테이블의 데이터가 중복되어 저장되지 않도록 방지한다. 어떤 열을 기본키로 설정한다면 해당 열에는 동일값을 저장할 수 없다.
엔티티 속성 구성하기
이제 SBB에서 쓸 엔티티를 만들면서 개념을 이해하자. 엔티티는 데이터베이스 테이블과 매핑되는 자바클래스를 말한다. 우리가 만들고 있는 SBB는 질문답변을 할 수 있는 게시판 서비스이므로 SBB의 질문과 답변 데이터를 저장할 데이터베이스 테이블과 매핑되는 질문과 답변 엔티티가 있어야한다.
엔티티를 모델 또는 도메인 모델이라고도 한다.
먼저, 질문과 답변 엔티티에는 각각 어떤 속성이 필요한지 생각해보자. 우리가 만들려는 SBB 게시판에는 사용자가 질문을 남기고 답변을 받는 웹서비스이므로, 사용자가 입력한 질문을 저장, 질문의 제목, 내용을 담는 항목이 필요하다. 그러므로 질문의 ‘제목’과 ‘내용’ 등을 엔티티 속성으로 추가해야 한다. 질문 엔티티의 경우:
속성 이름 | 설명 |
---|---|
id | 질문 데이터의 고유번호 |
subject | 질문 데이터의 제목 |
content | 질문 데이터의 내용 |
createDate | 질문 데이터의 작성 일자 |
답변 엔티티의 경우:
속성 이름 | 설명 |
---|---|
id | 답변 데이터의 고유 번호 |
question | 질문 데이터(무슨 질문의 답변인지?) |
content | 답변 데이터의 내용 |
creatDate | 답변 데이터의 작성 일자 |
이렇게 생각한 속성을 바탕으로 질문과 답변에 해당하는 엔티티를 작성합니다.
질문 엔티티 만들어보기
src/main/java
디렉터리의 com.mysite.sbb
패키지에 Question.java
파일을 작성해 Question 클래스를 만듭니다.
package com.mysite.sbb;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Question{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 200)
private String subject;
@Column(columnDefinition = "Text")
private String content;
private LocalDateTime createDate;
}
코드를 살펴보면, 엔티티로 만들기 위해서 Question 클래스에 @Entity
에너테이션을 적용했다.
이와 같은 @Entity
에너테이션을 적용해야 스프링부트가 Question 클래스를 엔티티로서 인식할 수 있다.
NOTE Getter, Setter 메서드를 자동생성하기 위해 롬복의
@Getter
,@Setter
애너테이션을 적용한다.
@Id 애너테이션?
id 속성에 적용한 @Id 애너테이션은 id 속성을 기본키로 지정한다. id 속성의 고유 번호는 엔티티 내에서 각 데이터들을 구분하는 유효한 값으로 중복되면 안되기 때문이다.
@GeneratedValue 에너테이션?
데이터를 저장할때 해당 속성에 일일히 값을 입력하지 않아도 자동으로 1씩 증가하여 저장된다.
strategy = GenerationType.IDENTITY
는 고유번호를 생성하는 방법을 지정하는 부분으로 GenerationType.IDENTITY
는 해당 속성만 별도로 번호가 차례대로 늘어나도록 할 때 사용한다.
strategy 옵션을 생략하면
@GenerateValue
애너테이션이 지정된 모든 속성에 번호를 차례대로 생성하므로 순서가 일정한 고유 번호를 갖을수 없게된다. 이 때문에strategy = GenerationType.IDENTITY
를 사용한다.
@Column 애너테이션?
엔티티 속성은 테이블 열 이름과 일치하는데 열의 세부설정을 위해 @Column
애너테이션을 사용한다. length는 열의 길이를(여기선 200), columnDefinition은 열 데이터의 유형이나 성격을 정의할 때 사용한다. columnDefinition = “TEXT” 는 “텍스트”를 열 데이터로 넣을 수 있음을 의미하고 글자 수 를 제한할 수 없는 경우에 사용한다.
NOTE
엔티티 속성은@Column
애너테이션을 사용하지 않더라도 테이블의 열로 인식한다. 테이블의 열로 인식하고 싶지않으면@Transient
애너테이션을 사용한다. 이 애터네이션은 엔티티 속성을 테이블의 열로 만들지 않고 클래스 속성 기능으로만 사용하고자 할때 쓴다.
NOTE
엔티티의 속성 이름과 테이블의 열 이름의 차이는? Question 엔티티에서 작성 일자인createDate
속성의 이름은 데이터베이스의 테이블에선create_date
라는 열 이름으로 설정된다. 즉,createDate
처럼 카멜 케이스 형식명은 전부create_date
같은 소문자로 변경 되고 언더바(_)로 구분되어 데이터베이스 테이블의 열 이름이 된다.
Warning
엔티티를 만들 때Setter
메서드는 사용하지 않는다. 일반적으로Setter
메서드는 사용하지 않는다. 엔티티는 DB와 직접 연결되어 데이터를 자유롭게 변경이 가능하므로Setter
메서드는 안전하지 않다.
NOTE
엔티티는 생성자에 의해서만 엔티티의 값을 저장할 수 있도록 하고, 데이터를 변경해야할 경우에는 메서드를 추가 작성해 값을 저장한다. 이 튜토리얼에서는 설명을 위해 Setter메서드를 사용하였다.
답변 엔티티 만들기
1) src/main/java
디렉터리의 com.mysite.sbb
패키지에 Answer.java
파일을 작성한다.
package com.mysite.sbb;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime createDate;
private Question question;
}
NOTE
각 속성은 질문 엔티티와 같다.
답변 엔티티에서는 질문 엔티티를 참조하기 위해 question
속성을 추가했다.
2) 답변을 통해 질문의 제목을 알려면 answer.getQuestion().getSubject()
를 사용해 접근할 수 있다. 하지만 question
속성만 추가하며 안 되고 질문 엔티티와 연결된 속성이라는 것을 답변 엔티티에 표기해야한다.
question
속성에 @ManyToOne
애너테이션을 추가해 질문 엔티티와 연결한다.
package com.mysite.sbb;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime createDate;
@ManyToOne
private Question question;
}
게시판 서비스에선 하나의 질문에 답변은 여러개가 달릴 수 있다. 따라서 답변은 Many가 되고 질문은 One 이 된다. @ManyToOne
에너테이션은 N:1 관계를 나타낸다.
이렇게 @ManyToOne
에너테이션을 설정하면 답변 엔티티의 question 속성과 Question 엔티티가 연결된다.
NOTE
실제 데이터베이스에서는 외래키(Foreign Key) 관계가 성립한다. *@ManyToOne
은 부모 자식 관계를 갖는 구조에서 사용한다. 부모:Question, 자식:Answer. * 외래키는 테이블과 테이블 사이의 관계를 구성할 때 연결되는 열을 의미한다.
3) 반대로 질문에서 답변을 참조하자, 답변과 질문이 N:1 관계이면 질문과 답변은 1:N 관계다. 이 경우 @OneToMany
애너테이션을 적용한다.
질문 하나에 답은 여럿이므로 Question 엔티티에 추가할 Answer 속성은 List 형태로 구성한다. 이를 위해 Question 엔티티를 다음과 같이 수정한다.
package com.mysite.sbb;
import java.time.LocalDateTime;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 200)
private String subject;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime createDate;
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;
}
Answer
객체들로 구성된 answerList
를 Question
엔티티의 속성으로 추가하고 @OneToMany
애너테이션을 설정했다.
질문에서 답변을 참조하려면 question.getAnswerList()
를 호출하면 된다.
@OneToMany
애너테이션에 사용된 mappedBy
는 참조 엔티티의 속성명을 정의한다.
즉, Answer
엔티티에서 Question
엔티티를 참조한 속성인 question
을 mappedBy
에 전달해야 한다.
CascadeType.REMOVE 란? 질문 하나에 답변이 여러개 달릴 수 있는데 보통 게시판에선 질문을 삭제하면 그에 따른 답변도 모두 삭제된다. SBB도 질문을 삭제하면 딸린 답변도 모두 삭제되도록
cascade = CascadeType.REMOVE
를 사용했다. * Cascade(종속)
테이블 확인하기
질문 답변 엔티티를 모두 만들었다면 이제 H2 콘솔로 들어간다.
이와 같이 엔티티를 통해 Question과 Answer 테이블이 자동 생성된 것을 알 수 있다.