이제 SBB가 사용할 엔티티(Entity)을 만들어 보자.
엔티티는 데이터베이스 테이블과 매핑되는 자바 클래스를 말한다. SBB는 질문과 답변을 할 수 있는 게시판 서비스이다.
따라서 SBB에는 질문과 답변에 해당하는 엔티티가 있어야 한다.
엔티티는 모델 또는 도메인 모델이라고 부르기도 한다.
이것들을 구분하지 않고 테이블과 매핑되는 클래스를 엔티티라 지칭하겠다.
엔티티의 속성 구상하기
그렇다면 질문과 답변 엔티티에는 어떤 속성들이 필요한지 먼저 생각해 보자.
질문(Question) 엔티티에는 최소한 다음과 같은 속성이 필요하다.
속성명설명
id | 질문의 고유 번호 |
subject | 질문의 제목 |
content | 질문의 내용 |
create_date | 질문을 작성한 일시 |
마찬가지로 답변(Answer) 엔티티에는 최소한 다음과 같은 속성이 필요하다.
속성명설명
id | 답변의 고유 번호 |
question | 질문 (어떤 질문의 답변인지 알아야하므로 질문 속성이 필요하다) |
content | 답변의 내용 |
create_date | 답변을 작성한 일시 |
이렇게 생각한 속성을 바탕으로 질문(Question)과 답변(Answer)에 해당되는 엔티티를 작성해 보자.
질문 엔티티 작성하기
다음과 같이 Question 클래스를 작성하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/Question.java]
package com.mysite.sbb;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
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;
}
엔티티로 만들기 위해 Question 클래스에 @Entity 어노테이션을 적용했다.
@Entity 어노테이션을 적용해야 JPA가 엔티티로 인식한다.
그리고 Getter, Setter 메서드를 자동으로 생성하기 위해 롬복의 @Getter, @Setter 어노테이션을 적용했다.
컨트롤러에 @Controller 애너테이션을 적용하는 것과 마찬가지로 엔티티는 @Entity 애너테이션을 적용해야 한다.
그리고 엔티티의 속성으로 고유번호(id), 제목(subject), 내용(content), 작성일시(createDate)를 추가했다.
각 속성에는 Id, GeneratedValue, Column과 같은 어노테이션이 적용되어 있는데 그것들에 대해서 하나씩 알아보자.
@Id
고유 번호 id 속성에 적용한 @Id 어노테이션은 id 속성을 기본 키로 지정한다. 기본 키로 지정하면
이제 id 속성의 값은 데이터베이스에 저장할 때 동일한 값으로 저장할 수 없다.
고유 번호를 기본 키로 한 이유는 고유 번호는 엔티티에서
각 데이터를 구분하는 유효한 값으로 중복되면 안 되기 때문이다.
데이터베이스에서는 id와 같은 특징을 가진 속성을 기본 키(primary key)라고 한다.
@GeneratedValue
@GeneratedValue 어노테이션을 적용하면 데이터를 저장할 때
해당 속성에 값을 따로 세팅하지 않아도 1씩 자동으로 증가하여 저장된다.
strategy는 고유번호를 생성하는 옵션으로
GenerationType.IDENTITY는 해당 컬럼만의 독립적인 시퀀스를 생성하여 번호를 증가시킬 때 사용한다.
strategy 옵션을 생략할 경우에 @GeneratedValue 어노테이션이 지정된 컬럼들이
모두 동일한 시퀀스로 번호를 생성하기 때문에 일정한 순서의 고유번호를 가질수 없게 된다.
이러한 이유로 보통 GenerationType.IDENTITY를 많이 사용한다.
@Column
엔티티의 속성은 테이블의 컬럼명과 일치하는데 컬럼의 세부 설정을 위해 @Column 어노테이션을 사용한다.
length는 컬럼의 길이를 설정할때 사용하고 columnDefinition은 컬럼의 속성을 정의할 때 사용한다.
columnDefinition = "TEXT"은 "내용"처럼 글자 수를 제한할 수 없는 경우에 사용한다.
엔티티의 속성은 @Column 어노테이션을 사용하지 않더라도 테이블 컬럼으로 인식한다.
테이블 컬럼으로 인식하고 싶지 않은 경우에만 @Transient 애너테이션을 사용한다.
테이블의 컬럼명
위의 Question 엔티티에서 작성일시에 해당하는 createDate 속성의 실제 테이블의 컬럼명은 create_date가 된다.
즉 createDate처럼 대소문자 형태의 카멜케이스(Camel Case) 이름은 create_date 처럼 모두 소문자로 변경되고
언더바(_)로 단어가 구분되어 실제 테이블 컬럼명이 된다.
답변 엔티티 생성하기
이어서 다음과 같이 답변 엔티티도 작성하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/Answer.java]
package com.mysite.sbb;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.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;
}
Id, content, createDate 속성은 질문 엔티티와 동일하므로 설명은 생략한다.
question 속성은 답변 엔티티에서 질문 엔티티를 참조하기 위해 추가했다.
답변 객체(예:answer)를 통해 질문 객체의 제목을 알고 싶다면 answer.getQuestion().getSubject()처럼 접근할 수 있다.
하지만 이렇게 속성만 추가하면 안되고 질문 엔티티와 연결된 속성이라는 것을 명시적으로 표시해야 한다.
즉, 다음과 같이 question 속성에 @ManyToOne 어노테이션을 추가해야 한다.
(... 생략 ...)
import javax.persistence.ManyToOne;
(... 생략 ...)
@Getter
@Setter
@Entity
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(columnDefinition = "TEXT")
private String content;
@CreatedDate
private LocalDateTime createDate;
@ManyToOne
private Question question;
}
답변은 하나의 질문에 여러개가 달릴 수 있는 구조이다.
따라서 답변은 Many(많은 것)가 되고 질문은 One(하나)이 된다.
따라서 @ManyToOne은 N:1 관계라고 할 수 있다.
이렇게 @ManyToOne 어노테이션을 설정하면 Answer 엔티티의 question 속성과 Question 엔티티가 서로 연결된다.
(실제 데이터베이스에서는 ForeignKey 관계가 생성된다.)
@ManyToOne은 부모 자식 관계를 갖는 구조에서 사용한다.
여기서 부모는 Question, 자식은 Answer라고 할 수 있다.
그렇다면 반대방향, 즉 Question 엔티티에서 Answer 엔티티를 참조할수는 없을까?
가능하다.
답변과 질문이 N:1의 관계라면 질문과 답변은 1:N의 관계라고 할 수 있다.
이런경우에는 @ManyToOne이 아닌 @OneToMany어노테이션을 사용한다.
Question 하나에 Answer는 여러개이므로 Question 엔티티에 추가할 답변의 속성은 List 형태로 구성해야 한다.
이를 구현하기 위해 Question 엔티티를 다음과 같이 수정하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/Question.java]
package com.mysite.sbb;
import java.time.LocalDateTime;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.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를 속성으로 추가하고 @OneToMany 어노테이션을 설정했다.
이제 질문 객체(예:question)에서 답변을 참조하려면 question.getAnswerList()를 호출하면 된다.
@OneToMany 어노테이션에 사용된 mappedBy는 참조 엔티티의 속성명을 의미한다.
즉, Answer 엔티티에서 Question 엔티티를 참조한 속성명 question을 mappedBy에 전달해야 한다.
CascadeType.REMOVE
질문 하나에는 여러개의 답변이 작성될 수 있다.
이때 질문을 삭제하면 그에 달린 답변들도 모두 함께 삭제하기 위해서
@OneToMany의 속성으로 cascade = CascadeType.REMOVE를 사용했다.
테이블 확인하기
Question과 Answer 엔티티를 작성한후 H2 콘솔에 접속해 보자.
Question과 Answer 테이블이 자동으로 생성된 것을 확인할 수 있다.
'JAVA > SpringBoot CRUD Board' 카테고리의 다른 글
[VSCODE] SpringBoot CRUD게시판 만들기 - 도메인 별로 분류하기 (0) | 2022.07.01 |
---|---|
[VSCODE] SpringBoot CRUD게시판 만들기 - 레포지토리(Repository) (0) | 2022.07.01 |
[VSCODE] SpringBoot CRUD게시판 만들기 - JPA (0) | 2022.06.30 |
[VSCODE] SpringBoot CRUD게시판 만들기 - 컨트롤러 (0) | 2022.06.30 |
[VSCODE] SpringBoot CRUD게시판 만들기 - 프로젝트의 구조 (0) | 2022.06.30 |