Spring Data JPA를 쓰다 보면, 엔티티 간 관계 설정은 피할 수 없는 주제입니다. 잘못 설계하면 성능 문제부터 N+1, 무한 루프, 삭제 오류 등 여러 문제가 발생하죠
1:N, N:M 연관관계의 개념과 설정법, 그리고 Cascade 옵션의 의미와 주의할 점까지 실무 위주로 정리합니다.
1. 연관관계의 기본 개념
JPA는 객체지향적인 데이터 모델을 추구합니다. 이를 위해 @OneToMany
, @ManyToOne
, @ManyToMany
같은 어노테이션을 사용해 엔티티 간 관계를 정의합니다.
- 1:N: 하나의 부모가 여러 자식을 가짐 (ex. 하나의 게시글에 여러 댓글)
- N:1: 여러 자식이 하나의 부모를 참조함 (ex. 여러 댓글이 하나의 게시글을 참조)
- N:M: 양쪽 다 다수 관계 (ex. 사용자와 역할 - 유저는 여러 역할을, 역할은 여러 유저를 가질 수 있음)
2. 1:N / N:1 연관관계 설정
예시: 게시글(Post)과 댓글(Comment)
@Entity
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
}
@Entity
public class Comment {
@Id @GeneratedValue
private Long id;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
}
주의할 점:
mappedBy
는 연관관계의 주인이 아님을 의미 – 실제 외래키는 Comment 쪽이 관리fetch = LAZY
를 명시하지 않으면, 무조건 즉시 로딩(EAGER)되어 성능 문제가 발생할 수 있음
3. N:M 연관관계 설정
N:M은 가능하면 중간 테이블을 엔티티로 분리하는 것을 추천합니다.
예시: User와 Role
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String username;
@ManyToMany
@JoinTable(name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
}
@Entity
public class Role {
@Id @GeneratedValue
private Long id;
private String roleName;
@ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
}
그러나 실무에서는 user_role 테이블 자체를 엔티티(UserRole 등)로 분리하여 생성일, 승인여부 등 추가 필드를 넣는 경우가 많습니다.
4. Cascade 옵션
Cascade는 연관된 엔티티의 생명주기를 함께 관리하겠다는 의미입니다.
PERSIST
– 부모 저장 시 자식도 저장MERGE
– 병합 시 같이 병합REMOVE
– 삭제 시 자식도 삭제ALL
– 위 모든 옵션 포함
주의!
- DELETE ON CASCADE처럼 작동하지만, 트랜잭션 단위로 동작하기 때문에 주의 깊게 설정해야 합니다.
- 회원 - 주문 관계에서 주문 삭제 시 회원이 삭제되면 안 되듯, 항상 비즈니스 도메인에 맞게 Cascade를 결정하세요.
5. orphanRemoval 옵션
orphanRemoval = true
는 컬렉션에서 빠진 자식 엔티티를 자동으로 삭제합니다. 단, 실제 DB DELETE가 발생하므로 정말 필요할 때만 설정해야 합니다.
6. 정리 및 실무 팁
- 연관관계의 주인은 항상 @ManyToOne 쪽
- LAZY 로딩을 기본값으로, 필요한 경우에만 EAGER
- 복잡한 N:M은 중간 테이블을 별도 엔티티로 설계
- 불필요한 Cascade 설정은 지양 – 삭제 오류, 데이터 유실 위험
- orphanRemoval은 컬렉션 변경 시 DELETE 실행됨
'개발 > JAVA' 카테고리의 다른 글
[JAVA] N+1 문제와 해결 방법 – Fetch Join과 @EntityGraph로 해결하기 (0) | 2025.10.15 |
---|---|
[JAVA] JPA Fetch 전략 완벽 가이드 – Lazy vs Eager 실무 적용법 (0) | 2025.10.14 |
[JAVA] JPQL과 @Query 사용법 – Spring Data JPA에서 복잡한 쿼리 다루기 (0) | 2025.10.12 |
[JAVA] JPA Repository 메서드 쿼리 작성법 – 실무 중심 가이드 (0) | 2025.10.11 |
[JAVA] @Entity, @Id, @GeneratedValue 완벽 가이드 – JPA 기본부터 실무 팁까지 (0) | 2025.10.10 |