노트

JPA 연관관계 매핑

JPA Association Mapping

백엔드#jpa · 연결된 개념 8개

쉽게 말하면

JPA 연관관계 매핑은 학생 명찰에 적힌 '3반'처럼 테이블의 외래키를 객체끼리의 참조로 옮기는 방법이에요. 반을 실제로 바꾸는 건 명찰 쪽이고, 반 명단은 그걸 비춰 보는 거울이죠.

비유가 깨지는 곳 거울(mappedBy 쪽) 컬렉션만 고치면 DB엔 반영되지 않아요. 양쪽 참조를 같이 맞추는 편의 메서드를 두고, ToOne 관계의 기본값이 EAGER이니 보통 전부 LAZY로 바꿔 둬요.

JPA(Jakarta Persistence, 옛 이름 Java Persistence API) 연관관계 매핑(association mapping)은 테이블의 외래키(foreign key) 관계를 엔티티 사이의 참조로 표현하는 방법이다. 핵심은 외래키를 관리하는 쪽(연관관계의 주인, owning side)을 정하는 것이고, 양방향 관계에서 반대쪽은 mappedBy로 "나는 읽기 전용 거울"이라고 표시한다.

관계 종류

@Entity
public class Student {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "class_id")          // 외래키 컬럼. 이쪽이 주인
    private SchoolClass schoolClass;
}
 
@Entity
public class SchoolClass {
    @OneToMany(mappedBy = "schoolClass")    // 거울. 외래키를 바꾸지 않음
    private List<Student> students = new ArrayList<>();
}
  • N:1 (@ManyToOne): 가장 흔하다. 외래키가 있는 "다" 쪽이 주인이다
  • 1:N (@OneToMany): 보통 양방향의 반대쪽으로 mappedBy와 함께 쓴다. 단방향 1:N은 외래키가 다른 테이블에 있어 UPDATE가 추가로 나가므로 피한다
  • 1:1 (@OneToOne): 외래키를 어느 테이블에 둘지 고른다
  • N:M (@ManyToMany): 중간 테이블(join table)이 필요하다(@JoinTable). 실무에서는 중간 테이블에 수량·날짜 같은 컬럼이 붙기 마련이라, 중간 엔티티를 만들고 1:N + N:1 두 개로 푸는 편이 낫다

주의할 점

  • 주인이 아닌 쪽(mappedBy)의 컬렉션만 바꾸면 DB에는 반영되지 않는다. 양쪽 참조를 함께 맞추는 편의 메서드(convenience method)를 둔다
  • 기본 FetchType은 @ManyToOne·@OneToOne이 EAGER, 컬렉션이 LAZY다. 보통 전부 LAZY로 두고 필요한 곳에서 함께 가져온다(지연 로딩, N+1 문제)
  • 부모 저장·삭제를 자식에게 전파하려면 cascade를 쓴다
  • 엔티티에 Lombok @Data를 쓰면 toString·hashCode가 양방향 참조를 따라가 무한 순환하거나 지연 로딩을 건드린다. @Getter 정도만 쓴다(Lombok)
  • 엔티티를 그대로 JSON으로 내보내도 같은 순환이 생긴다. DTO로 바꿔 응답한다(스프링 REST 컨트롤러)

관계를 어디까지 묶어 한 단위로 다룰지는 DDD의 애그리거트 경계와 같은 질문이다. 외래키 자체의 의미는 참조 무결성을 본다.

책: 6장. 메모리에서 데이터베이스로 — JPA

출처: Hibernate ORM User Guide: Associations · @ManyToOne · @OneToMany · @ManyToMany

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • Spring Data 리포지터리와 쿼리 메서드

    Spring Data는 인터페이스만 선언하면 구현체를 런타임에 만들어 주는 영속성 추상화(persistence abstraction)다. JpaRepository<엔티티, ID타입>을 상속하면 저장·조회·삭제·페이징이 바로 생기고, 메서드 이름 규칙만으로 쿼리를 만들 수도 있다.

  • JPA Auditing

    JPA Auditing은 엔티티의 생성·수정 시각과 생성자·수정자를 저장할 때 자동으로 채워 주는 Spring Data JPA 기능이다. 모든 엔티티에 반복되는 createdAt = now() 코드를 없앤다.

  • JPQL과 @Query

    JPQL(Jakarta Persistence Query Language)은 테이블이 아니라 엔티티와 필드를 대상으로 쓰는 JPA의 객체 지향 쿼리 언어다. 실행할 때 연결된 DB의 SQL로 번역된다. Spring Data에서는 @Query로 리포지터리 메서드에 직접 붙인다.

  • JDBC와 JdbcTemplate

    JDBC(Java Database Connectivity)는 자바가 여러 DB와 통신하기 위한 표준 API이고, Hibernate·MyBatis·스프링 JDBC 모두 그 위에 있다. JdbcTemplate은 스프링이 JDBC의 반복 작업(연결 열고 닫기, 예외 변환, 결과 순회)을 대신해 주는 클래스다.

  • 영속성 컨텍스트

    트랜잭션 동안 엔티티를 보관하고 변경을 추적하는 JPA의 작업 공간.

보기 옵션