9osari.log
← 목록으로

[JPA] 연관관계 매핑

객체와 테이블의 연관관계 차이

테이블의 연관관계

테이블은 외래키 하나로 양방향 조회가 가능하다.

-- 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만 탐색 가능)

@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();  // ❌ 불가능! 참조가 없음

양방향 연관관계

양방향 연관관계를 만들려면 단방향 연관관계 2개를 만들어야 한다.

@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)

@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가 주인일까?


양방향 매핑 시 주의사항

순수 객체 상태를 고려해 항상 양쪽에 값을 설정하자

잘못된 예시 →

//주인 쪽만 설정
Team team = new Team();
em.persist(team);

Member member = new Member();
member.setTeam(team);  // 주인 쪽만 설정
em.persist(member);

// Team에서 조회하면?
team.getMembers().size();  // 0 (양쪽 설정 안해서 비어있음)

옳게된 예시 →

//양쪽 모두 설정
Team team = new Team();
em.persist(team);

Member member = new Member();
member.setTeam(team);  // 주인 쪽 설정
team.getMembers().add(member);  // 반대편도 설정
em.persist(member);

하나하나 신경쓰기 귀찮으니 연관관계 편의 메서드를 작성하자

@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
    
    // 연관관계 편의 메서드
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);  // 양쪽 모두 설정
    }
}
// 사용
Member member = new Member();
member.changeTeam(team);  // 한 번에 양쪽 설정!

다양한 연관관계 매핑

N:1 (@ManyToOne)

@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

1:N (@OneToMany)

@Entity
public class Team {
    @OneToMany
    @JoinColumn(name = "team_id")  // 반대 테이블의 외래키 관리
    private List<Member> members = new ArrayList<>();
}

1:1 (@OneToOne)

@Entity
public class Member {
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
}

N:M (@ManyToMany)

@Entity
public class Member {
    @ManyToMany
    @JoinTable(name = "member_product")
    private List<Product> products = new ArrayList<>();
}

해결 → 중간 엔티티를 만들어 N:1, 1:N 으로 풀자

@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단계: 단방향 매핑으로 설계 완료
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

양방향은 필요할 때 추가하면 된다.

이유 →

단방향 매핑만으로 이미 연관관계 매핑은 완료된다. 양방향 설계는 객체 입장에서 고민만 많아지니 단방향으로 설계를 완료한 후 양방향은 필요한 경우 추가하자!

···
← PREV [JPA] 프록시, 지연로딩, 영속성 전이 NEXT → [JPA] JPQL과 페치 조인