
엔티티(Entity)란 데이터베이스의 테이블과 매핑되는 자바 클래스를 의미한다. 즉, 데이터를 관리하는 데 사용하는 ORM의 자바 클래스가 바로 엔티티.
컨트롤러 클래스에 @Controller 어노테이션을 추가해 스프링 컨테이너에게 웹 요청을 처리하는 컨트롤러 역할을 한다고 알려 줬던 것처럼, 엔티티 클래스에도 @Entity 어노테이션을 적용해야 한다.
// 질문 엔티티
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 Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 200)
private String subject;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime createDate;
}
@Entity@Getter, @Setter@Id@GeneratedValuestrategy = GenerationType.IDENTITY고유한 번호를 생성하는 전략을 지정하는 옵션
IDENTITY: 데이터베이스의 AUTO_INCREMENT 기능을 사용하여 자동으로 증가흔 값 생성
이 옵션을 지정하지 않으면 모든 엔티티에서 번호를 공유하게 되어 예측 가능한 순서를 보장할 수 없음
// AUTO: JPA가 데이터베이스에 맞게 자동 선택
@GeneratedValue(strategy = GenerationType.AUTO)
// SEQUENCE: 데이터베이스 시퀀스 사용
@GeneratedValue(strategy = GenerationType.SEQUENCE)
// TABLE: 별도 테이블을 사용하여 키 생성
@GeneratedValue(strategy = GenerationType.TABLE)
@Column// 컬럼 길이 설정 (기본값: VARCHAR(255))
@Column(length = 200)
private String subject;
// 컬럼 데이터 타입 직접 지정
@Column(columnDefinition = "TEXT")
private String content;
// null 허용 여부 (기본값: true)
@Column(nullable = false)
private String title;
// 유니크 제약 조건
@Column(unique = true)
private String email;@Transient@Entity
public class User {
@Id
private Long id;
private String password;
@Transient // 데이터베이스에 저장되지 않음
private String confirmPassword; // 비밀번호 확인용 임시 필드
}보통의 게시판 서비스에서는 하나의 질문에 여러 개의 답변이 달릴 수 있다. 게시물이 지워진다 해서 하단의 답변이 지워지지 않지만, 게시물이 지워진다면 그 게시물의 아래에 있던 답변들은 연쇄적으로 사라진다. 따라서 답변 엔티티는 질문 엔티티를 참조하는 구조가 필요하다.
// 기본 Answer 엔티티 생성
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;
}
위와 같이 단순히 private Question question만 추가하면 JPA가 이 속성의 관계를 이해할 수 없다. JPA에게 이것이 어떤 종류의 관계인지 알 수 있도록 명시적으로 작성해 주어야 한다.
@ManyToOne// @ManyToOne으로 관계 설정한 Answer 엔티티 클래스
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;
}
-- 생성되는 테이블 구조
CREATE TABLE answer (
id INTEGER NOT NULL AUTO_INCREMENT,
content TEXT,
create_date DATETIME,
question_id INTEGER, -- 외래키: Question 테이블의 id를 참조
PRIMARY KEY (id),
FOREIGN KEY (question_id) REFERENCES question(id)
);
@OneToManyList<Answer> 형태로 관리mappedBy = “question”// Answer 엔티티의 question 속성과 연결
public class Answer {
@ManyToOne
private Question question; // ← 이 속성명과 mappedBy 값이 일치해야 함
}
// Question 엔티티에서 참조
@OneToMany(mappedBy = "question") // Answer의 "question" 속성을 참조
private List<Answer> answerList;Cascade// 모든 연산을 연쇄적으로 수행
@OneToMany(cascade = CascadeType.ALL)
// 삭제 시 연쇄 수행
@OneToMany(cascade = CascadeType.REMOVE)
// 저장 시에만 연쇄 수행
@OneToMany(cascade = CascadeType.PERSIST)
// 여러 옵션 동시 적용
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})// @OneToMany으로 관계 설정한 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 엔티티 클래스는 Question 엔티티 클래스의 자식 관계이므로 반드시 Question을 참조하여야 하지만, Question 엔티티는 Answer 엔티티를 무조건 참조할 필요는 없다. 이때 Question에서 Answer를 참조하느냐에 따라 단방향 관계와 양방향 관계가 결정된다.
// 단방향
// Answer에서만 Question 참조 가능
Answer answer = answerRepository.findById(1);
Question question = answer.getQuestion(); // ✅ 가능
// 양방향
// Question에서 Answer 목록 조회 가능
Question question = questionRepository.findById(1);
List<Answer> answers = question.getAnswerList(); // ✅ 가능
// Answer에서 Question 조회도 가능
Answer answer = answerRepository.findById(1);
Question question = answer.getQuestion(); // ✅ 가능
JPA에서는 양방향 관계에서 데이터 정합성을 보장하기 위해 하나의 엔티티만 외래키를 관리하도록 설정하고 있다. 만약 양쪽 모두 외래키를 관리한다면 데이터 불일치가 발생할 수 있기 때문에, 일반적으로는 외래키를 실제로 가지고 있는 테이블(= Answer)의 엔티티가 연관관계의 주인이 된다.