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의 애그리거트 경계와 같은 질문이다. 외래키 자체의 의미는 참조 무결성을 본다.
출처: Hibernate ORM User Guide: Associations · @ManyToOne · @OneToMany · @ManyToMany