[JPA] 연관관계 매핑
      [JPA] 연관관계 매핑
      
    
    
    
  
  객체와 테이블의 연관관계 차이
테이블의 연관관계
테이블은 외래키 하나로 양방향 조회가 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- MEMBER 테이블
ID (PK)
USERNAME
TEAM_ID (FK)
-- TEAM 테이블
ID (PK)
NAME
-- 양방향 조회가 모두 가능
-- 멤버 → 팀 조회
SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.ID;
-- 팀 → 멤버 조회
SELECT * FROM TEAM T JOIN MEMBER M ON T.ID = M.TEAM_ID;
외래키 TEAM_ID 하나만 있으면 양쪽 다 조회 가능 (양방향)
객체의 연관관계
객체는 참조(reference)를 사용해 연관관계를 갖는다.
단방향 연관관계 (Member → Team만 탐색 가능)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Entity
public class Member {
    @Id
    private Long id;
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;  // Member에서 Team 참조
}
@Entity
public class Team {
    @Id
    private Long id;
    private String name;
    // Team에서 Member 참조 없음!
}
// Member → Team 조회 가능
Member member = em.find(Member.class, 1L);
Team team = member.getTeam();  // ✅ 가능
// Team → Member 조회 불가능
Team team = em.find(Team.class, 1L);
List<Member> members = team.getMembers();  // ❌ 불가능! 참조가 없음
- 이 상태는 단방향 Member → Team만 가능
양방향 연관관계
양방향 연관관계를 만들려면 단방향 연관관계 2개를 만들어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Entity
public class Member {
    @Id
    private Long id;
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;  // 1. Member → Team 참조
}
@Entity
public class Team {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();  // 2. Team → Member 참조
}
// 양방향 조회 모두 가능
Member member = em.find(Member.class, 1L);
Team team = member.getTeam();  // Member → Team
Team team = em.find(Team.class, 1L);
List<Member> members = team.getMembers();  // Team → Member
그럼 양쪽에서 참조를 가지고 있으면 누가 외래키를 관리해야 할까?
연관관계의 주인 (mappedBy)
- 연관관계 주인만이 외래키를 관리(등록/수정) 즉 외래키가 있는 곳을 주인으로 정하자
- 주인이 아닌 쪽은 읽기만 가능하다.
- 주인은 mappedBy속성을 사용하지 않는다
- 주인이 아닌쪽에서 mappedBy로 주인을 지정한다.
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;  // 연관관계의 주인 (외래키 관리)
}
@Entity
public class Team {
    @OneToMany(mappedBy = "team")  // 주인 아님 (읽기만 가능)
    private List<Member> members = new ArrayList<>();
}
왜 Member가 주인일까?
- MEMBER 테이블에 TEAM_ID (FK) 가 있기 때문에
- N:1 관계에서 항상 N쪽이 외래키를 가진다.
- 따라서 @ManyToOne쪽이 연관관계의 주인
양방향 매핑 시 주의사항
순수 객체 상태를 고려해 항상 양쪽에 값을 설정하자
잘못된 예시 →
1
2
3
4
5
6
7
8
9
10
//주인 쪽만 설정
Team team = new Team();
em.persist(team);
Member member = new Member();
member.setTeam(team);  // 주인 쪽만 설정
em.persist(member);
// Team에서 조회하면?
team.getMembers().size();  // 0 (양쪽 설정 안해서 비어있음)
옳게된 예시 →
1
2
3
4
5
6
7
8
//양쪽 모두 설정
Team team = new Team();
em.persist(team);
Member member = new Member();
member.setTeam(team);  // 주인 쪽 설정
team.getMembers().add(member);  // 반대편도 설정
em.persist(member);
하나하나 신경쓰기 귀찮으니 연관관계 편의 메서드를 작성하자
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
    
    // 연관관계 편의 메서드
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);  // 양쪽 모두 설정
    }
}
1
2
3
// 사용
Member member = new Member();
member.changeTeam(team);  // 한 번에 양쪽 설정!
다양한 연관관계 매핑
N:1 (@ManyToOne)
1
2
3
4
5
6
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}
- 외래키가 있는 쪽이 연관관계 주인
- 가장 많이 사용한다.
- 양방향 시 Team에 @OneToMany(mappedBy = "team")추가
1:N (@OneToMany)
1
2
3
4
5
6
@Entity
public class Team {
    @OneToMany
    @JoinColumn(name = "team_id")  // 반대 테이블의 외래키 관리
    private List<Member> members = new ArrayList<>();
}
- 1 이 연관관계의 주인
- 다른 테이블의 외래키를 관리해 복잡하다.
- 실무에서는 N:1 양방향을 사용
1:1 (@OneToOne)
1
2
3
4
5
6
@Entity
public class Member {
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
}
- 주 테이블이나 대상 테이블 중 외래키 선택 가능
N:M (@ManyToMany)
1
2
3
4
5
6
@Entity
public class Member {
    @ManyToMany
    @JoinTable(name = "member_product")
    private List<Product> products = new ArrayList<>();
}
- 실무에서 사용하지 않음
- 중간 테이블이 숨겨져 있어 예상치 못한 쿼리 발생
- 중간 테이블에 추가 정보를 넣을 수 없다.
해결 → 중간 엔티티를 만들어 N:1, 1:N 으로 풀자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public class MemberProduct {
    @Id @GeneratedValue
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;
    
    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
    
    private int orderAmount;  // 추가 정보 가능
}
실전 팁
먼저 단방향 매핑으로 설계하자
1
2
3
4
5
6
7
// 1단계: 단방향 매핑으로 설계 완료
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}
- 단방향 매핑만으로 이미 연관관계 매핑은 완료된다.
양방향은 필요할 때 추가하면 된다.
- JPQL 역방향 탐색이 필요한 경우
- 
    객체 그래프 탐색이 필요한 경우 1 2 3 4 5 6 // 2단계: 필요하면 양방향 추가 @Entity public class Team { @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>(); } 
이유 →
- 테이블에 영향을 주지 않는다 (테이블은 외래키만으로 충분)
- 단방향 매핑으로 이미 연관관계 설정 완료
- 양방향은 객체 그래프 탐색 편의를 위한 것
- 양방향 설계는 객체 입장에서 고민만 많아진다…
단방향 매핑만으로 이미 연관관계 매핑은 완료된다. 양방향 설계는 객체 입장에서 고민만 많아지니 단방향으로 설계를 완료한 후 양방향은 필요한 경우 추가하자!
        
          
          This post is licensed under 
        
          CC BY 4.0
        
         by the author.