1. 다대일 (@ManyToOne)
여러 명의 회원이 하나의 팀에 속한다.
- 특징: 데이터베이스 테이블 구조상 외래 키(FK)는 N(다) 쪽인 회원(MEMBER) 테이블에 존재한다.
- 가장 흔하게 사용되는 연관관계. (기본적인 필수 매핑 방식)
- 기준 객체가 N쪽. (본인 테이블에 FK가 있음)
- 연관관계의 주인으로, FK를 직접 관리/등록/수정
- 예: 회원과 팀
- 구현 방법: N에 해당하는 테이블(MEMBER)에 1에 해당하는 테이블(TEAM)의 PK를 FK로 추가한다.
- 작동: member.setTeam(team)을 호출하고 저장하면 바로 MEMBER 테이블의 TEAM_ID FK 값이 업데이트된다.
- 장점: 객체의 참조(team)와 테이블의 외래 키(TEAM_ID) 위치가 일치하므로 직관적이고 관리가 매우 쉽다.
@Entity
public class Member { // 'N' 입장
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // MEMBER 테이블의 team_id FK와 매핑
private Team team;
}
💡 한쪽 테이블의 레코드가 다른 테이블의 여러 레코드와 연결될 수 있는 구조로, 엄밀히 일대다와는 다르다.
💡 다대일과 일대다는 관계를 바라보는 기준(시선)의 차이가 다르다. 주로 테이블의 외래 키(FK) 위치와 연관관계의 주인 개념을 기준으로 구분한다.
ℹ️ @JoinColumn을 사용하는 이유 (역할)
- 개념 차이 해결(데이터 연관짓는 방식): 자바 객체는 참조(team)로 관계를 맺고, DB 테이블은 외래 키 컬럼(TEAM_ID)으로 관계를 맺음.
- 외래 키(FK) 이름 지정: 객체의 참조 필드가 실제 DB의 어떤 외래 키 컬럼과 연결될지 이름을 명시.
- 올바른 연관관계 저장: 객체 모델과 DB 테이블 모델 간의 차이를 매핑하여 데이터가 정확하게 저장 및 조회되도록 돕는다.
2. 일대다 (@OneToMany)
하나의 팀이 여러 명의 회원을 가진다.
- 특징: 1(일) 쪽인 팀(Team) 객체 입장에서 N(다) 목록(members)을 관리하는 방식.
- 상대방(N) 테이블에 FK가 있음
- 연관관계의 주인이 아니므로, mappedBy로 읽기 전용으로 설정하는 것이 일반적
- 단독 매핑보다는 @ManyToOne과 함께 양방향으로 사용 권장 (아래 [5번]에 추가 설명)
- 예: 팀과 회원 (팀이 기준)
- ⚠️ 주의점 (단방향 일대다 매핑의 단점):
- 객체는 Team이 회원 목록을 관리하지만, 정작 DB의 외래 키(FK)는 MEMBER 테이블에 있다.
- 따라서 team.getMembers().add(member)로 회원을 추가하면, Team을 저장/수정할 때 MEMBER 테이블에 UPDATE 쿼리가 추가로 실행되는 단점이 발생한다.
@Entity
public class Team { // '1' 입장
@Id @GeneratedValue
private Long id;
@OneToMany
@JoinColumn(name = "team_id") // 상대방(MEMBER) 테이블의 FK를 지정해야 함
private List<Member> members = new ArrayList<>();
}
💡 Tip!
항상 외래 키(FK)가 위치한 엔티티가 연관관계의 주인(Owner)이 되도록 매핑하고, 주인이 아닌 쪽은 mappedBy 속성을 지정하는 것이 기본 원칙.
ℹ️ JPA에서는 주 테이블에서 대상 테이블의 FK를 직접 관리하는 단방향 매핑을 지원하지 않으므로, 반드시 양방향 매핑(mappedBy)으로 설정해야 한다.
3. 일대일 (@OneToOne)
하나의 회원이 하나의 보관함만 가진다
- 특징: 양쪽 엔티티가 서로 단 하나씩만 연결되는 구조.
- 주 테이블이나 대상 테이블 중 어느 곳에나 외래 키(FK)를 배치할 수 있다.
- 주로 보안상의 이유로 테이블을 분리하거나, 특정 데이터가 자주 쓰이지 않아 테이블을 쪼갤 때 사용한다.
- 예: 회원과 보관함, 회원과 여권
- 구현 방법: 어느 한쪽 테이블의 PK를 다른 쪽 테이블의 FK로 지정하되, 그 FK에 UNIQUE(중복 방지) 제약 조건을 걸어준다.
- 동작 원리 (주테이블에 FK 배치):
- Member 엔티티가 Locker 엔티티의 참조를 가진다.
- @JoinColumn(name = "locker_id")로 지정된 컬럼에 상대방의 PK 값이 저장된다.
- FK에 UNIQUE 제약조건이 추가되어 1:1 관계가 유지된다.
- 동작 원리 (주테이블에 FK 배치):
- 외래 키(FK) 위치 선택:
- 주 테이블에 FK 배치 (권장): 주 테이블만 조회해도 대상 테이블과의 연관관계를 바로 확인할 수 있어 개발자들이 선호.
- JPA 조회가 단순하고 빠르다. (객체지향적 조회의 편리성)
- 대상 테이블에 FK 배치: 전통적인 DB 개발자들이 선호하며, 관계가 1:N으로 확장될 때 테이블 구조를 유지하기 유리하다. (mappedBy 필수)
- DB 외래 키는 Locker에 있지만, 자바 객체의 주도권(연관관계의 주인)을 Member로 두고 싶거나 반대로 설정할 때 사용한다.
- 주 테이블에 FK 배치 (권장): 주 테이블만 조회해도 대상 테이블과의 연관관계를 바로 확인할 수 있어 개발자들이 선호.
@Entity
public class Member { // 주 테이블
@Id @GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "locker_id") // 외래 키(FK)를 주 테이블(Member)이 관리
private Locker locker;
}
@Entity
public class Locker { // 대상 테이블 (양방향 설정 시)
@Id @GeneratedValue
private Long id;
@OneToOne(mappedBy = "locker") // 읽기 전용 (Member 엔티티의 locker 필드)
private Member member;
}
4. 다대다 (@ManyToMany)
여러 명의 학생이 여러 개의 과목을 신청해 듣는다
- 특징: 양쪽 테이블(엔티티)가 서로 상대방의 레코드를 여러 개 가질 수 있는 구조
- 관계형 데이터베이스는 정규화된 2개의 테이블만으로 N:M 관계를 표현할 수 없다.
- 따라서 중간에 두 테이블 사이에 관계를 중개해 주는 연결 테이블(Junction/Association Table)을 두고, 1:N, N:1 관계로 풀어내야 한다. (아래 구현 방법에 대한 내용은 [5번]에 추가 설명)
- 예: 학생과 과목, 회원과 상품
- 관계형 데이터베이스는 정규화된 2개의 테이블만으로 N:M 관계를 표현할 수 없다.
- 구현 방법: 중간에 연결 테이블을 생성해, 각각의 PK를 모아 외래키로 연결하는 1:N 관계 2개로 풀어낸다.
- 예: 수강신청 테이블을 생성하여 학생ID와 과목ID를 저장
- ⚠️ 주의점 (실무 사용 금지 권장):
- @ManyToMany 어노테이션을 사용하면 JPA가 자동으로 중간 연결 테이블을 생성해주지만, 이 테이블에는 연결용 FK 외에 다른 컬럼(예: 수강 신청일, 주문 수량, 생성일자 등)을 추가할 수 없습니다.
- 또한 시스템이 예상치 못한 복잡한 중간 테이블 쿼리를 실행하므로 실무에서는 사용을 지양합니다.
// ❌ [비권장] @ManyToMany 단순 매핑 방식
@Entity
public class Student {
@Id @GeneratedValue
private Long id;
@ManyToMany
@JoinTable(name = "student_course") // 자동으로 생성될 연결 테이블명
private List<Course> courses = new ArrayList<>();
}
5. 실무 권장 패턴
1) 양방향 매핑 (1:N / N:1 관계인 경우)
- 특징: 단순 1:N / N:1 관계에서 양쪽에서 서로 참조 가능하도록 설정하는 기법 (단방향 조회만 가능하던 것을 반대쪽에서도 객체 참조(조회)할 수 있도록 양방향화)
- @ManyToOne을 연관관계의 주인(FK 관리자)으로 두고, 필요할 때 @OneToMany를 추가하여 읽기전용 양방향 관계를 만듦 (가장 표준적인 전략)
- 원래부터 N 쪽에 FK 존재했기 때문에 DB 테이블에는 아무 변화가 없다. (원래 MEMBER 테이블에 있던 team_id FK 사용)
- 구현:
- Member(N) : @ManyToOne (외래 키 관리자 / 주인)
- Team(1) : @OneToMany(mappedBy = "team") (읽기 전용)
// N (다) - 외래 키를 가지며 연관관계의 주인
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
// 1 (일) - 읽기 전용 (mappedBy 지정)
@Entity
public class Team {
@OneToMany(mappedBy = "team") // Member 엔티티의 'team' 필드에 의해 매핑됨을 명시
private List<Member> members = new ArrayList<>();
}
💡요약: DB 테이블의 외래 키(FK)가 위치한 '다(N)' 쪽 엔티티에 @ManyToOne을 설정해 FK를 관리하게 하고, '일(1)' 쪽에서 조회 기능이 필요하면 @OneToMany(mappedBy = "...")를 추가하는 것이 안전하다.
2) 중간 엔티티(연결 테이블) 승격 (N:M 관계인 경우)
- 특징: 다대다 관계를 중간 엔티티(테이블)를 직접 만들어 양쪽에 실무에서 사용 가능한 형태(1:N + N:1)로 분해(매핑)하는 설계 방식. (가장 안전)
- @ManyToMany 대신 중간 연결 엔티티를 두고, 양쪽에 @ManyToOne 2개 작성
- DB에 중간 엔티티와 그 전용 테이블이 새로 생성 및 추가된다. (Orders(주문)라는 중간 엔티티 + ORDERS 테이블 생성)
- JPA가 자동으로 만드는 N:M 연결 테이블의 한계(추가 컬럼 불가능)를 극복.
- 구현:
- Member(1) ↔ Orders(N) ↔ Item(1)
- Orders 엔티티가 Member와 Item의 FK를 각각 @ManyToOne으로 가지며, orderCount, orderDate 같은 추가 필드를 마음껏 정의할 수 있다.
// 1. 회원 (1)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "member")
private List<Orders> orders = new ArrayList<>();
}
// 2. 주문 (중간 엔티티 - N:M을 1:N, N:1로 해쇄)
@Entity
public class Orders {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "item_id")
private Item item;
private int orderCount; // 연결 테이블에 필요한 추가 컬럼 작성 가능!
private LocalDateTime orderDate;
}
// 3. 상품 (1)
@Entity
public class Item {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "item")
private List<Orders> orders = new ArrayList<>();
}
6. 연관 관계 데이터 조회하기 (JOIN)
- 위에서 설정된 연관 관계를 바탕으로 데이터를 함께 묶어서 조회할 때는 JOIN 문을 사용한다.
-- 부서와 사원 테이블을 연결하여 조회하는 예시
SELECT e.사원명, d.부서명
FROM 사원 e
INNER JOIN 부서 d ON e.부서ID = d.부서ID;
'Language & Tool > sql' 카테고리의 다른 글
| 🗄️CASE문 문법 및 활용법 총정리 (Simple / Searched / SUM() 활용 / ORDER BY절에서의 사용) (0) | 2026.07.06 |
|---|---|
| 🗄️서브쿼리 개념 종류 및 JOIN과의 차이점 완벽 정리 (0) | 2026.07.02 |
| 🗄️주요 JOIN의 종류 완벽 정리(Inner, Left, Cross, Self 등) (0) | 2026.07.02 |
| 🗄️SQL 쿼리 작성 순서와 실행 순서 (0) | 2026.07.02 |