Spring_25_JPA 양방향 관계·참조

OngTK·2025년 11월 6일

Spring

목록 보기
25/25

JPA 양방향 관계·참조 정리


1) 핵심 개념 요약

  • 연관관계의 주인(owning side): 외래키(FK)를 가진 쪽. @JoinColumn 을 선언하는 필드가 주인.
  • 비주인(inverse side): mappedBy = "주인측필드명" 으로 매핑. DB 변경은 직접 못 하고 읽기 전용 뷰 성격.
  • 양방향 동기화: 한쪽에만 세팅하면 깨질 수 있음 → 편의 메서드(양방향 세팅 함수) 로 양쪽을 동시에 세팅.

2) 어노테이션(심화) — 필수 옵션 설명

@Entity                      : 클래스 ↔ 테이블 매핑
@Id                          : PK 지정
@Table(name=...)             : 테이블명 지정
@GeneratedValue(strategy=GenerationType.IDENTITY)
                             : MySQL의 AUTO_INCREMENT
@Column(...)                 : 컬럼 상세 옵션
  - nullable / unique / name / length / insertable / updatable

주의: @GeneratedValue 철자 정확히(오타 @GeneratedValued 금지)


3) 관계 매핑 추가(또는 보완)

3-1. Category(1) ↔ Board(N)

  • 외래키는 보통 Board 테이블에 있으므로, Board 쪽 필드(Category를 참조하는 기존 필드명)가 주인입니다.
  • 절대 필드명 변경 금지: 아래 코멘트 위치에 이미 존재하는 필드에만 어노테이션 부여.
// BoardEntity.java
// (기존에 CategoryEntity를 참조하는 필드가 있다고 가정)
// 예) private CategoryEntity category;  // ← "category" 라는 이름이 아니어도 됨. 기존 이름 유지!

// [추가/보완] 외래키를 가진 주인쪽 어노테이션만 부여
@ManyToOne(fetch = FetchType.LAZY)               // 지연로딩 권장
@JoinColumn(name = "category_id")                // FK 컬럼명 (DB에 맞게 유지/수정)
private CategoryEntity [기존_카테고리_참조_필드명];  
// CategoryEntity.java
// (이미 Board 리스트를 보유한 컬렉션 필드 존재 가정;)
// 예) private List<BoardEntity> boards = new ArrayList<>();

// [추가/보완] 비주인(inverse) 쪽 설정
@OneToMany(mappedBy = "[Board에서_Category를_가리키는_필드명]",
           cascade = CascadeType.ALL,
           orphanRemoval = true)
private List<BoardEntity> [기존_보드_컬렉션_필드명];

mappedBy 값은 BoardEntity에서 Category를 참조하는 필드의 정확한 이름이어야 합니다.
예: Board 쪽 필드명이 categoryRef 라면 mappedBy = "categoryRef".


3-2. Board(1) ↔ Reply(N)

  • 외래키는 보통 Reply 테이블에 있으므로, Reply 쪽 필드(Board를 참조하는 기존 필드명)가 주인입니다.
// ReplyEntity.java
// (BoardEntity를 참조하는 기존 필드가 있다고 가정)
// 예) private BoardEntity board;

// [추가/보완] 주인쪽 어노테이션
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private BoardEntity [기존_보드_참조_필드명];
// BoardEntity.java
// (Reply 컬렉션 보유한 기존 필드 가정)
// 예) private List<ReplyEntity> replies = new ArrayList<>();

// [추가/보완] 비주인쪽 설정
@OneToMany(mappedBy = "[Reply에서_Board를_가리키는_필드명]",
           cascade = CascadeType.ALL,
           orphanRemoval = true)
private List<ReplyEntity> [기존_리플_컬렉션_필드명];

mappedBy 값은 ReplyEntity에서 Board를 참조하는 필드의 정확한 이름이어야 합니다.


4) 양방향 편의 메서드(동기화)

// BoardEntity.java
public void addReply(ReplyEntity reply) {
    this.[기존_리플_컬렉션_필드명].add(reply);
    reply.set[Board_참조_필드의_Setter](this); // 예: reply.setBoard(this);
}

public void removeReply(ReplyEntity reply) {
    this.[기존_리플_컬렉션_필드명].remove(reply);
    reply.set[Board_참조_필드의_Setter](null);
}
// CategoryEntity.java
public void addBoard(BoardEntity board) {
    this.[기존_보드_컬렉션_필드명].add(board);
    board.set[Category_참조_필드의_Setter](this); // 예: board.setCategory(this);
}

public void removeBoard(BoardEntity board) {
    this.[기존_보드_컬렉션_필드명].remove(board);
    board.set[Category_참조_필드의_Setter](null);
}

5) Lazy 로딩·N+1 관련 팁

  • @ManyToOne(fetch = FetchType.LAZY) 명시 권장.
  • @OneToMany 는 기본이 LAZY.
  • 필요 시 페치 조인 또는 @EntityGraph 사용.
    (레포지토리 메서드 시그니처/반환타입은 유지, 어노테이션만 추가)
// 예시: Board와 replies를 한 번에
//@EntityGraph(attributePaths = "[기존_리플_컬렉션_필드명]")
//Optional<BoardEntity> findById(Long id);

6) CASCADE

cascade = CascadeType.ALL : 부모가 삭제/수정되면 자식도 함께 삭제/수정 (권장)
cascade = CascadeType.PERSIST : 부모가 저장되면 자식도 저장
cascade = CascadeType.MERGE : 부모가 수정되면 자식도 수정
cascade = CascadeType.REMOVE : 부모가 삭제되면 자식도 삭제
cascade = CascadeType.REFRESH : 부모가 재호출(갱신)되면 자식도 재호출(갱신)
cascade = CascadeType.DETACH : 부모가 영속 해제되면 자식도 영속 해제

profile
2025.05.~K디지털_풀스택 수업 수강중

0개의 댓글