JPA 연관관계 및 참조 복습
이 문서는 제공된 수업 자료 및 실습 코드를 바탕으로 JPA의 핵심 개념인 객체 참조, 연관관계 설정(단방향, 양방향), 그리고 주요 어노테이션(@ManyToOne, @OneToMany) 및 속성(cascade, fetch)에 대해 정리합니다.
1. 자바 객체 참조: JPA 연관관계의 기초
JPA의 연관관계는 데이터베이스의 테이블 관계를 자바 객체 세상에서 표현하는 것입니다. 이를 이해하기 위해 먼저 순수 자바 객체 간의 참조 관계를 살펴보겠습니다.
example2/day03/_자바참조 패키지의 예제는 Category와 Board 객체를 통해 이를 설명합니다.
Board.java: 게시물 객체는 자신이 속한 카테고리 객체 하나를 참조합니다.// Board.java public class Board { private int bno; private String btitle; private String bcontent; private Category category; // FK **단방향** 참조 }Category.java: 카테고리 객체는 자신에게 속한 여러 게시물 객체를List로 참조할 수 있습니다.// Category.java public class Category { private int cno; private String cname; @ToString.Exclude // 순환참조 방지 List<Board> boardList = new ArrayList<>(); // board 여러개를 참조하여 **양방향** }Example.java단방향 참조: 게시물 객체에서
setCategory()를 통해 카테고리 객체를 참조하면,board.getCategory()를 통해 언제든지 자신과 연결된 카테고리 정보를 가져올 수 있습니다.// 게시물 객체가 카테고리 객체를 참조 Board board1 = new Board(); board1.setCategory(category1); // board1을 통해 category1의 이름 조회 가능 System.out.println(board1.getCategory().getCname());양방향 참조: 반대로 카테고리 객체에서도 자신에게 속한 게시물들을 관리하려면, 카테고리의
boardList에 게시물 객체를 추가해야 합니다.// 카테고리 객체의 리스트에 게시물 객체를 추가 category1.getBoardList().add(board1); // category1을 통해 자신에게 속한 게시물 목록 조회 가능 System.out.println(category1.getBoardList());⚠️ 순환 참조 문제: 양방향 참조 시, 각 객체의
toString()메소드가 서로를 계속 호출하여StackOverflowError가 발생할 수 있습니다. 이를 방지하기 위해 한쪽의toString()에서 해당 필드를 제외해야 합니다. (@ToString.Exclude)
2. JPA 연관관계 매핑
JPA에서는 자바 객체 참조를 데이터베이스의 외래 키(FK) 관계로 매핑합니다. 이를 연관관계 매핑이라고 합니다.
2.1. 단방향 연관관계: @ManyToOne
BoardEntity가 CategoryEntity를 참조하는 것처럼, 다(N) 쪽에서 일(1) 쪽을 참조하는 관계입니다.
BoardEntity.java:N:1관계에서N에 해당하는 엔티티@Entity public class BoardEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int bno; // ... // N:1 (다수가 하나에게) @ManyToOne @JoinColumn(name = "cno") // DB에 생성될 FK 필드명 private CategoryEntity categoryEntity; }@ManyToOne: 다대일 관계임을 명시합니다.BoardEntity가 '다(Many)',CategoryEntity가 '일(One)'입니다.@JoinColumn(name = "cno"):eboard테이블에cno라는 이름의 외래 키 컬럼을 생성하여ecategory테이블의 PK와 연결합니다.
2.2. 양방향 연관관계: @OneToMany
단방향 관계에 더해, 일(1) 쪽에서 다(N) 쪽을 참조할 수 있도록 설정하는 것입니다.
CategoryEntity.java:1:N관계에서1에 해당하는 엔티티@Entity public class CategoryEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int cno; // ... // 1:N (하나가 다수에게) @OneToMany(mappedBy = "categoryEntity") // '연관관계의 주인'이 아님을 명시 @ToString.Exclude // 순환참조 방지 private List<BoardEntity> boardEntityList = new ArrayList<>(); }@OneToMany(mappedBy = "categoryEntity"): 일대다 관계임을 명시합니다.mappedBy = "categoryEntity": 매우 중요합니다. 이것은 이 관계가BoardEntity에 있는categoryEntity필드에 의해 매핑되었음을 나타냅니다. 즉,CategoryEntity는 연관관계의 주인이 아니며, 외래 키 관리를 하지 않습니다. 실제 DB 테이블에는 아무런 변화가 없으며, 오직 객체 참조를 위한 필드입니다. 연관관계의 주인은 항상 외래 키를 가지는 쪽(@ManyToOne이 있는 쪽)입니다.
3. 다대다(N:M) 관계 해결: 연결 엔티티 사용
실습3의 Course, Student, Enroll 예제는 다대다(N:M) 관계를 어떻게 모델링하는지 보여줍니다. 한 학생은 여러 과정을 수강할 수 있고, 한 과정에는 여러 학생이 등록할 수 있습니다.
JPA에서 @ManyToMany를 직접 사용할 수도 있지만, 보통은 중간에 연결 엔티티(Associative Entity)를 두어 두 개의 1:N 관계로 풀어내는 것을 권장합니다. 실습3에서는 EnrollEntity가 이 역할을 합니다.
CourseEntity (1)↔EnrollEntity (N)↔StudentEntity (1)EnrollEntity.java(연결 엔티티)@Entity public class EnrollEntity extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int enrollId; // 과정(Course)과 N:1 단방향 연결 @ManyToOne @JoinColumn(name = "courseId") private CourseEntity courseEntity; // 학생(Student)과 N:1 단방향 연결 @ManyToOne @JoinColumn(name = "studentId") private StudentEntity studentEntity; }EnrollEntity는CourseEntity와StudentEntity에 대한 외래 키를 각각 가집니다.
CourseEntity.java/StudentEntity.java// CourseEntity.java @OneToMany(mappedBy = "courseEntity") private List<EnrollEntity> enrollEntities = new ArrayList<>(); // StudentEntity.java @OneToMany(mappedBy = "studentEntity") private List<EnrollEntity> enrollEntities = new ArrayList<>();CourseEntity와StudentEntity는 각각EnrollEntity와 양방향 관계를 맺어, 자신과 관련된 수강 기록들을 참조할 수 있습니다.
4. 연관관계 주요 속성
4.1. 영속성 전이 (Cascade)
부모 엔티티의 영속성 상태 변화(저장, 수정, 삭제 등)를 자식 엔티티에게도 전파하는 옵션입니다.
BoardEntity.java의 예시@ManyToOne(cascade = CascadeType.ALL) private CategoryEntity categoryEntity;CascadeType종류ALL: 모든 영속성 상태 변화를 전파합니다. (저장, 수정, 삭제 등)PERSIST: 부모 엔티티를 저장(persist)할 때 자식 엔티티도 함께 저장됩니다.MERGE: 부모 엔티티를 수정(merge)할 때 자식 엔티티도 함께 수정됩니다.REMOVE: 부모 엔티티를 삭제(remove)할 때 자식 엔티티도 함께 삭제됩니다.REFRESH: 부모 엔티티를 새로고침할 때 자식도 새로고침됩니다.DETACH: 부모 엔티티가 영속성 컨텍스트에서 분리되면 자식도 분리됩니다.
4.2. 로딩 전략 (Fetch)
연관된 엔티티를 언제 데이터베이스에서 조회할지 결정하는 전략입니다.
BoardEntity.java의 예시@ManyToOne(fetch = FetchType.LAZY) private CategoryEntity categoryEntity;FetchType종류EAGER(즉시 로딩): 부모 엔티티를 조회할 때 연관된 자식 엔티티도 즉시 함께 조회합니다.@ManyToOne의 기본값입니다. 불필요한 조회를 유발하여 성능 저하의 원인이 될 수 있습니다.LAZY(지연 로딩): 부모 엔티티를 조회할 때는 연관된 자식 엔티티를 조회하지 않고, 실제 그 자식 엔티티를 사용하는 시점(board.getCategory())에 조회합니다.@OneToMany의 기본값이며, 성능 최적화를 위해 권장됩니다.
5. 공통 필드 상속: @MappedSuperclass
실습3의 BaseTime 클래스는 여러 엔티티에서 공통으로 사용되는 필드(생성일, 수정일)를 상속하기 위한 클래스입니다.
BaseTime.java@Getter @MappedSuperclass // 엔티티 상속 전용 클래스 @EntityListeners(AuditingEntityListener.class) // JPA Auditing 기능 활성화 public class BaseTime { @CreatedDate // 생성 시간 자동 기록 private LocalDateTime createdDate; @LastModifiedDate // 수정 시간 자동 기록 private LocalDateTime updatedDate; }@MappedSuperclass: 이 클래스는 테이블로 매핑되지 않고, 자식 엔티티에게 필드만 상속해주는 역할을 합니다.@EntityListeners(AuditingEntityListener.class): 엔티티의 변화를 감지하여createdDate,updatedDate를 자동으로 관리해줍니다. (메인 클래스에@EnableJpaAuditing필요)- 각 엔티티에서는
extends BaseTime으로 상속받아 사용합니다.
'백엔드 > 스프링' 카테고리의 다른 글
| [Spring] JPA 실습 복습 자료 (0) | 2025.11.05 |
|---|---|
| [Spring] JWT 토큰과 Security 인증 인가 로직 복습 (0) | 2025.10.22 |
| [Spring] 암호화(BCrypt) 및 쿠키(Cookie) 복습 자료 (0) | 2025.10.21 |
| [Spring] Redis 개념 및 활용법 with Spring Boot (0) | 2025.10.21 |
| [Spring] MyBatis XML 연동 개요 (0) | 2025.10.13 |
| [Spring] 도서 대여 콘셉트 트랜잭션 실습과 피드백 마크다운 (1) | 2025.09.26 |
| [Spring] 13주차+: 마이크로서비스 아키텍처(MSA) 입문 (0) | 2025.09.22 |
| [Spring] 12주차: 설정 분리 및 비동기 처리 (0) | 2025.09.22 |