JPA의 @ManyToOne 관계와 조인 전략
JPA(Java Persistence API)를 사용하면 데이터베이스 작업을 객체 지향적으로 처리할 수 있다. 그러나 엔티티 간의 관계 설정과 쿼리 방식에 따라 애플리케이션의 성능과 데이터 무결성이 크게 달라질 수 있다.
이 글에서는 @ManyToOne
연관 관계를 중심으로, JPA의 조인 전략과 @JoinColumn
의 nullable
속성이 쿼리에 미치는 영향을 살펴보고, 최적의 설정 방법을 제시한다.
1. 엔티티 관계와 @ManyToOne
다음 예제 코드를 통해 @ManyToOne
관계를 설정하는 방법을 살펴보자.
Team 엔티티
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "members") // 순환 참조 방지
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team") // 양방향 관계
private List<Member> members = new ArrayList<>();
// 양방향 관계 편의 메서드
public void addMember(Member member) {
member.setTeam(this);
members.add(member);
}
}
Member 엔티티
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "team") // 순환 참조 방지
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
@JoinColumn(name = "TEAM_ID", nullable = false) // NULL 허용 여부 설정
private Team team;
}
Member
엔티티는 Team
엔티티와 @ManyToOne
관계를 가진다. @JoinColumn(name = "TEAM_ID")
을 통해 Member
테이블에 외래 키(Foreign Key) 컬럼을 생성한다.
2. fetch 속성: 즉시 로딩 vs. 지연 로딩
@ManyToOne
어노테이션의 fetch
속성은 연관된 엔티티를 언제 가져올지를 결정한다.
fetch 속성 | 설명 | 장점 | 단점 |
---|---|---|---|
EAGER |
Member 조회 시 Team 도 즉시 조회 |
한 번의 쿼리로 모두 조회 | 불필요한 데이터 로딩 가능 |
LAZY |
Member 조회 시 Team 은 필요할 때 조회 |
성능 최적화 가능 | 추가 쿼리 발생 가능 (N+1 문제) |
N+1 문제를 방지하려면 JOIN FETCH
(JPQL) 또는 @EntityGraph
를 활용하는 것이 좋다.
3. nullable 속성: NULL 허용 여부와 조인 전략
@JoinColumn
의 nullable
속성은 외래 키 컬럼의 NULL 허용 여부를 설정하며, JPA가 생성하는 조인 쿼리의 종류에 영향을 준다.
nullable 속성 | 설명 | 조인 방식 |
---|---|---|
true (기본값) |
외래 키 컬럼이 NULL을 허용 | LEFT OUTER JOIN |
false |
외래 키 컬럼이 NULL을 허용하지 않음 | INNER JOIN |
NULL 허용 여부에 따른 조인 방식 변화
-
nullable = true
→LEFT OUTER JOIN
사용 (연결된Team
이 없어도Member
조회 가능) -
nullable = false
→INNER JOIN
사용 (연결된Team
이 없는Member
는 조회되지 않음)
INNER JOIN이 LEFT OUTER JOIN보다 성능상 유리한 경우가 많다.
4. 실험: fetch와 nullable 조합에 따른 쿼리 비교
EAGER + nullable = true (LEFT OUTER JOIN)
SELECT m.MEMBER_ID, t.TEAM_ID, t.name, m.username
FROM Member m
LEFT JOIN Team t ON t.TEAM_ID = m.TEAM_ID
WHERE m.MEMBER_ID = ?;
EAGER + nullable = false (INNER JOIN)
SELECT m.MEMBER_ID, m.TEAM_ID, t.TEAM_ID, t.name, m.username
FROM Member m
JOIN Team t ON t.TEAM_ID = m.TEAM_ID
WHERE m.MEMBER_ID = ?;
LAZY (JOIN 없음, 추가 쿼리 발생)
-- Member 조회 (Team 조인 없음)
SELECT m.MEMBER_ID, m.TEAM_ID, m.username
FROM Member m
WHERE m.MEMBER_ID = ?;
-- findMember.getTeam().getName() 호출 시 추가 쿼리 실행
SELECT t.TEAM_ID, t.name
FROM Team t
WHERE t.TEAM_ID = ?;
em.getReference(Team.class, id)
를 사용하면 프록시 객체가 반환된다.
5. 최적의 설정 선택 가이드
✅ 데이터 무결성을 유지하려면:
-
nullable = false
(모든Member
는 반드시Team
에 속해야 함) -
INNER JOIN
을 통해 성능 향상 가능
✅ 성능 최적화를 원한다면:
-
FetchType.LAZY
사용 (불필요한 데이터 로딩 방지) - 필요 시
JOIN FETCH(JPQL)
또는@EntityGraph
활용
✅ 유연한 데이터 모델이 필요하다면:
-
nullable = true
(소속되지 않은Member
도 허용) -
LEFT OUTER JOIN
을 사용하여 데이터 손실 방지
✅ ID만 필요할 경우:
-
em.getReference()
활용 (불필요한 데이터 조회 방지)
결론
JPA의 조인 전략과 NULL 제약 조건을 적절히 활용하면 애플리케이션의 성능과 데이터 무결성을 동시에 확보할 수 있다.
프로젝트에서 요구하는 데이터 모델과 성능 요건을 고려하여 최적의 설정을 선택하는 것이 중요하다.
Top comments (0)