티스토리 뷰
데이터베이스의 구성 요소
- 테이블(table) : 데이터 저장 공간, 2차원의 표의 형태
- 행(row) : 가로줄
- 열(column) : 세로줄
- 기본키(primary key, PK) : 한 테이블에서 데이터를 구분하게 하는 고유한 값. 중복된 값을 갖지 않음.
엔티티란
- 데이터베이스의 테이블과 매핑되는 자바 클래스를 엔티티라고 한다.
- "모델" 또는 "도메인 모델" 이라고도 한다.
질문(Question) 엔티티와 답변(Answer) 엔티티에 필요한 속성은 무엇이 있을까
질문(Question) | 답변(Answer) | 속성 이름 |
질문 고유 번호 | 답변 고유 번호 | id |
x | 질문 고유 번호 | question * 어떤 질문에 달린 답변인지 알기 위해서는 "질문" 테이블과 "답변" 테이블의 연계가 필요함. * 하나의 질문에 여러개의 답변이 달릴 수 있다. 1:N 관계 |
질문 제목 | x | subject |
질문 내용 | 답변 내용 | content |
질문 작성 날짜 | 답변 작성 날짜 | writeDate |
질문 수정 날짜 | 답변 수정 날짜 | modifyDate |
질문 작성자 | 답변 작성자 | user * 누가 작성했는지 알기 위해서는 "질문" 테이블과 "답변" 테이블에 각각에 유저정보 테이블과의 연계가 필요함. * 하나의 질문에 하나의 작성자. 1:1 관계 * 하나의 답변에 하나의 작성자. 1:1 관계 |
질문을 추천한 사람들 | 답변을 추천한 사람들 | voter * 하나의 질문에 여러명이 추천할 수 있다. * 한 사람은 여러개의 질문에 추천할 수 있다. * 하나의 답변에 여러명이 추천할 수 있다. * 한 사람은 여러개의 답변에 추천할 수 있다. * 질문과 질문추천인은 N:M관계 * 답변과 답변추천인은 N:M관계 * 질문과 질문추천인 테이블과의 연계 필요 * 답변과 답변추천인 테이블과의 연계 필요 --> N:M 관계는 질문추천인, 답변추천인 테이블과 같이 테이블을 생성해서 관리해야 한다. |
... | ... | ... |

여기부턴 내용 많음 주의...
글을 작성하는 시점에서는 교재의 모든 기능 구현이 완료한 상태여서
교재의 후반부에 추가한 기능까지 한꺼번에 내용을 작성했습니다.
전체 프로젝트 구조는 이렇고,
여기서 Question.java , Answer.java, SiteUser.java 코드를 설명해보겠습니다.
Question 엔티티 코드
package com.study.board.question;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import com.study.board.answer.Answer;
import com.study.board.siteUser.SiteUser;
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.ManyToMany;
import jakarta.persistence.ManyToOne;
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 writeDate;
private LocalDateTime modifyDate;
//OneToMany : Question 하나에 Answer 여러개 달림
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;
//ManyToOne : 많은 Question 을 한 사람(SiteUser)가 작성할 수 있음
// 이 어노테이션을 달면 DB의 QUESTION 테이블에 user_id 라는 컬럼이 생성된다.
// 컬럼 생성 규칙은 private SiteUser user; 에서 user라는 변수명에 _id 가 붙어 생성된다.
// user_id 컬럼은 SiteUSer 객체의 id 값을 담는 컬럼으로 생성된다.
@ManyToOne
private SiteUser user;
//하나의 질문에 여러사람이 추천할 수 있고, 한 사람은 여러 질문을 추천할 수 있음(N:M 관계)
// ManyToMany 관계는 아래와 같이 선언시, QUESTION_VOTER 라는 테이블이 생성된다.
@ManyToMany
Set<SiteUser> voter;
}
엔티티를 작성하기 위해서는 Question 클래스에 @Entity 라고 선언해야 한다.
해당 어노테이션을 적용해야 스프링 부트가 Question 클래스를 엔티티로 인식한다.
엔티티의 속성으로
id, subject, content, writeDate, modifyDate 를 작성했고,
answerList, user, voter 에는 특수한 어노테이션을 달아 작성했다.
@Id 어노테이션
- id 속성에 적용한 어노테이션으로, id 속성을 기본키로 지정하는 역할
- 질문 게시글 마다 데이터를 구분하는 값으로, 중복되면 안된다.
@GeneratedValue 어노테이션
- id 속성에 적용한 어노테이션으로, 데이터를 저장할 때 해당 속성에 값을 일일이 입력하지 않아도 자동 +1 증가하여 저장되게 한다.
- strategy = GenerationType.IDENTITY 는 고유한 번호를 생성하는 방법을 지정하는 부분으로, GenerationType.IDENTITY 라고 작성했기 때문에, 해당 속성(id 속성)만 번호가 차례대로 늘어나도록 할 때 사용한다.
* strategy 옵션을 생략한다면, @GeneratedValue 어노테이션이 지정된 모든 속성에 번호를 차례대로 생성하므로, 일정한 고유 번호를 가질 수 없게 된다. 이러한 이유로 보통 strategy = GenerationType.IDENTITY 를 많이 사용한다.
@Column 어노테이션
- 엔티티의 속성은 테이블의 열을 의미한다. 테이블의 열에 세부 설정이 필요할 때 해당 어노테이션을 사용한다.
- @Column(length = 200) : 열의 길이를 200 바이트로 제한한다.
- @Column(columnDefinition = "TEXT") : 텍스트를 열 데이터로 넣음을 의미, 글자 수를 제한할 수 없을 때 사용.
* 엔티티의 속성은 @Column 어노테이션을 사용하지 않아도 테이블의 열로 인식한다.
테이블의 열로 인식하고 싶지 않다면 @Transient 어노테이션을 사용한다.
@Transient 어노테이션은 엔티티의 속성을 테이블의 열로 만들지 않고, 클래스의 속성 기능으로만 사용하고자 할 때 쓴다.
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;
- 질문 하나에 여러개의 답변이 달릴 수 있다. (1:N 관계)
- 답변의 List를 가져와야 하므로 List<Answer> answerList; 라고 작성했다.
- @OneToMany 어노테이션을 사용하면 Question 과 Answer 이 연결되고,
question.getAnwerList(); 하여 질문 객체에서 답변들을 가져올 수 있게 된다.
* mappedBy는 참조 엔티티의 속성명을 정의한다. (Question 이니까 question 으로 작성)
* casecase = CascadeType.REMOVE : Question이 삭제되면, Question 밑에 달린 Answer도 모두 삭제되도록 한다.
casecade 에 대한 내용은 https://www.baeldung.com/jpa-cascade-types 참고
@ManyToOne
private SiteUser user;
- 많은 질문들을 한 사람(SiteUser)이 작성할 수 있다. = 한 사용자가 질문 여러개 등록 가능
- 이 어노테이션을 달면 DB의 QUSTION 테이블에 user_id 라는 컬럼이 생성된다.
- 컬럼 생성 규칙은 private SiteUser user; 에서 user 라는 변수명에 _id가 붙어 생성된다.
- user_id 컬럼은 SiteUser 엔티티 (SITE_USER 테이블)의 id 값을 담는 컬럼으로 생성된다.
**정리**
@ManyToOne 사용하면,
주체인 QUESTION 테이블에, SITE_USER 테이블의 키 값을 담는 user_id 컬럼이 생성된다.
이때 user_id 컬럼은 "외래키"가 된다.
@ManyToMany
Set<SiteUser> voter;
- 하나의 질문에 여러 사람이 추천할 수 있고, 한 사람은 여러 질문을 추천할 수 있다.
- 그러므로 N:M관계이다.
- 하나의 질문에 어떤 사람들이 추천했는지 가져오기 위해, Set<SiteUser> voter 로 지정했다.
- List 대신 Set으로 한 이유는 voter가 서로 중복되지 않기 위함이다.
- Set으로 선언 시, 사용자는 하나의 질문에 딱 1번만 추천할 수 있다.
- ManyToMany 선언 시, QUESTION_VOTER 라는 테이블이 생성된다.
**정리**
@ManyToMany 사용하면
Question 엔티티와 SiteUser 엔티티가 N:M 관계가 생기고,
QUESTION_VOTER 라는 테이블이 생긴다.
테이블 안에는 question_id, voter_id 라는 컬럼이 생긴다.
Answer 엔티티 코드
package com.study.board.answer;
import java.time.LocalDateTime;
import java.util.Set;
import com.study.board.question.Question;
import com.study.board.siteUser.SiteUser;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
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 writeDate;
private LocalDateTime modifyDate;
@ManyToOne
private Question question;
@ManyToOne
private SiteUser user;
@ManyToMany
Set<SiteUser> voter;
}
@ManyToOne 과 @ManyToMany 가 붙은 속성만 설명해보겠다.
@ManyToOne
private Question question;
- 여러개의 답변이 하나의 질문에 달릴 수 있다. (N:1 관계)
- @ManyToOne 어노테이션을 사용하면 Answer 과 Question 이 연결된다. 또한,
답변을 통해 질문의 제목을 알고 싶다면, answer.getQuestion().getSubject() 를 사용해서 가져올 수 있다.
**정리**
@ManyToOne 사용하면,
주체인 ANSWER 테이블에, QUESTION 테이블의 키 값을 담는 question_id 컬럼이 생성된다.
이때 question_id 컬럼은 "외래키"가 된다.
@ManyToOne
private SiteUser user;
- 많은 답변들을 한 사람(SiteUser)이 작성할 수 있다. = 한 사용자가 답변 여러개 등록 가능
- 이 어노테이션을 달면 DB의 ANSWER 테이블에 user_id 라는 컬럼이 생성된다.
- 컬럼 생성 규칙은 private SiteUser user; 에서 user 라는 변수명에 _id가 붙어 생성된다.
- user_id 컬럼은 SiteUser 엔티티 (SITE_USER 테이블)의 id 값을 담는 컬럼으로 생성된다.
**정리**
@ManyToOne 사용하면,
주체인 ANSWER 테이블에, SITE_USER 테이블의 키 값을 담는 user_id 컬럼이 생성된다.
이때 user_id 컬럼은 "외래키"가 된다.
@ManyToMany
Set<SiteUser> voter;
- 하나의 답변에 여러 사람이 추천할 수 있고, 한 사람은 여러 답변을 추천할 수 있다.
- 그러므로 N:M관계이다.
- 하나의 답변에 어떤 사람들이 추천했는지 가져오기 위해, Set<SiteUser> voter 로 지정했다.
- List 대신 Set으로 한 이유는 voter가 서로 중복되지 않기 위함이다.
- Set으로 선언 시, 사용자는 하나의 답변에 딱 1번만 추천할 수 있다.
- ManyToMany 선언 시, ANSWER_VOTER 라는 테이블이 생성된다.
SiteUser 엔티티 코드
회원가입, 로그인, 로그아웃 기능은 3장에서 구현한다.
다만 Quesetion과 SiteUser, Answer와 SiteUser 간의 관계를 설명하기 위해 미리 작성해두기로 한다.
package com.study.board.siteUser;
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 SiteUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
@Column(unique = true)
private String email;
}
** 사용자의 정보가 담기는 클래스명을 User가 아닌 SiteUser 라고 한 이유는,
스프링 시큐리티에 이미 User 클래스가 있기 때문이다.
물론 패키지가 달라 User 라는 이름을 사용할 수 있지만,
패키지 오용으로 오류가 발생할 수 있으므로 User 대신 SiteUser 라고 만들었다.
@Column(unique = true)
private String username;
- username 변수는 사용자의 로그인id가 담기는 속성이다. unique=true 선언하여 중복되지 않게 설정했다.
@Column(unique = true)
private String email;
- 이메일 또한 unique = true 선언하여 중복되지 않게 설정했다.
위와 같이 Question, Answer, SiteUser 엔티티를 작성 후 서버를 실행하면
자동으로 H2 데이터베이스에 작성한 엔티티에 대응하는 테이블이 생성되고,
그에 따른 컬럼도 생성된다.
Question, Answer, SiteUser 엔티티는
QUESTION, ANSWER, SITE_USER 테이블로 생성되었다.
<QUESTION 테이블 설명>
Question 엔티티의 id, subject, content, writeDate, modifyDate 속성은
ID, SUBJECT, CONTENT, WRITE_DATE, MODIFY_DATE 컬럼으로 생성되었다.
Question 엔티티에서 @ManyToOne 어노테이션이 달렸던 SiteUser user 속성은
USER_ID 컬럼으로 생성되었고, 이 값은 SITE_USER 테이블의 ID 값과 연결된다.
Question 엔티티에서 @ManyToMany 어노테이션이 달렸던 Set<SiteUser> voter 속성은
QUESTION_VOTER 테이블을 생성시켰고,
해당 테이블에는 QUESTION_ID (QUESTION 테이블의 ID) , VOTER_ID (SITE_USER 테이블의 ID) 컬럼이 생성되었다.
<ANSWER 테이블 설명>
id, content, writeDate, modifyDate는 위와 동일하다.
Answer 엔티티에서 @ManyToOne 어노테이션이 달렸던
Question question 과 SiteUser suser 속성은
QUESTION_ID, USER_ID 컬럼으로 생성되었고,
이 값들은 QUESTION 테이블과 SITE_USER 테이블의 ID 값과 연결된다.
ANSWER 엔티티에서 @ManyToMany 어노테이션이 달렸던 Set<SiteUser> voter 속성은
ANSWER_VOTER 테이블을 생성시켰고,
해당 테이블에는 ANSWER_ID (ANSWER 테이블의 ID) , VOTER_ID (SITE_USER 테이블의 ID) 컬럼이 생성되었다.
<SITE_USER 테이블 설명>
SiteUser 엔티티의 id, username, password, email 속성에 맞게
ID, USERNAME, PASSWORD, EMAIL 컬럼이 생성되었다.
1. 엔티티의 속성과 테이블 열 이름의 차이를 알아보자
Question 엔티티에서 작성 일시에 해당하는 writeDate 속성은
Question 테이블에서 write_date라는 열로 생성되었다.
즉, 카멜케이스 형식의 이름은 단어 사이에 언더바(_)로 구분되어 컬럼명이 만들어진다.
비슷하게, 테이블도 그러한 방식으로 생성된다.
SiteUser 라는 엔티티는 SITE_USER 테이블로 생성된다.
2. 엔티티를 만들 땐 Setter 메서드는 사용하지 않는다.
일반적으로 엔티티는 Setter 메서드를 사용하지 않도록 한다.
왜냐하면 엔티티는 데이터베이스와 바로 연결되기 때문에
Setter 메소드의 사용은 안전하지 않다고 판단되기 때문이다.
Setter 메서드 없이 어떻게 엔티티에 값을 저장할 수 있을까?
엔티티는 생성자에 의해서만 엔티티의 값을 저장할 수 있게 하고,
데이터를 변경해야 할 경우에는 메서드를 추가로 작성하면 된다.
** 이 교재는 복잡도를 낮추기 위해 엔티티에 @Setter 선언을 하고 진행하고 있다. **