
다중성 · 방향(단/양방향) · 연관관계의 주인
객체는 참조(Reference)로 관계를 탐색하고, 테이블은 외래키(FK)로 관계를 탐색한다.
이 간극을 메우는 것이 연관관계 매핑이며, 설계 시 아래 3가지 축을 먼저 결정한다.
✅ 가장 많이 씀
Member.team)으로 두고, 반대편(Team.members)은 일기용으로 둔다.@Entity
class Member {
@Id @GeneratedValue Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
class Team {
@Id @GeneratedValue Long id;
private String name;
}
@Entity
class Team {
@Id @GeneratedValue Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
양쪽 일관성을 맞추기 위해 한쪽에서 둘 다 세팅하는 메서드를 둔다.
// Member
public void changeTeam(Team team) {
this.team = team;
if (!team.getMembers().contains(this)) {
team.getMembers().add(this);
}
}
⚠️ 가능은 하나 권장 X
Member m = new Member("member1");
em.persist(m);
Team t = new Team("teamA");
t.getMembers().add(m); // 1쪽에서 관리
em.persist(t);
// 실제 실행: INSERT(member), INSERT(team), UPDATE(member.team_id=?)
결론: 일대다 단방향 보다 다대일 양방향을 쓰는게 좋음 (쿼리/운영 모두 깔끔!)
// 읽기 전용으로 만들어 충돌 방지(공식 양방향 아님)
@ManyToOne
@JoinColumn(name="TEAM_ID", insertable = false, updatable = false)
private Team team;
@Entity
class Member {
@Id @GeneratedValue Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID", unique = true) // UNIQUE 권장
private Locker locker;
}
@Entity
class Locker {
@Id @GeneratedValue Long id;
private String name;
@OneToOne(mappedBy = "locker") // 읽기 전용
private Member member;
}
그렇다면 주테이블 대상테이블은 누가 기준이 될까?
관례적으로 FK를 가진 테이블을 '주'로 잡는 편이 많지만, 실제론 "업무상 주로 조회/관리하는 쪽"을 주로 보는 팀도 있다. 핵심은 FK 위치가 주인 결정에 직접적이라는 점이다.
프록시와 지연로딩 이슈를 간단히 짚고 넘어가자면
대상 테이블에 FK가 있는 1:1 일부 케이스는 프록시 한계로 즉시 로딩이 강제될 수 있다. (세부 내용은 심화에서)
❌ 실무 비추천
@Entity
class Member {
@Id @GeneratedValue Long id;
@ManyToMany
@JoinTable(name = "member_product")
private List<Product> products = new ArrayList<>();
}
@Entity
class MemberProduct {
@Id @GeneratedValue Long id;
@ManyToOne @JoinColumn(name = "member_id")
private Member member;
@ManyToOne @JoinColumn(name = "product_id")
private Product product;
// 추가 컬럼
private int orderAmount;
private LocalDateTime orderedAt;
}
@ManyToMany는 피하고, MemberProduct 같은 중간 엔티티로 풀어
@ManyToOne + @OneToMany 조합을 쓰자!
💡 실무에서의 팁!
단방향으로 먼저 모델 완성하고 필요할 때만 반대편 참조를 추가 (양방향)
테이블 스키마에는 영향 없고, 코드 탐색성만 늘어난다.
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
// "나는 주인이 아니고, Member의 team 필드에 의해 매핑됐다"는 뜻
FK가 N쪽에 있으니 @ManyToOne + @JoinColumn이면 끝
변경도 N쪽만 수정하면 되어서 흐름이 단순하다.
조회가 불편하면 나중에 Team에 members를 추가해 양방향을 열면 된다.
A를 얻으면 B를 포기하는 균형/대가를 말한다.
예를들어 1:N(1이 주인)은 모델이 직관적일 수 있으나 쿼리가 늘고 운영 혼란이 증가한다.(대가)
단방향은 참조 보유쪽에 FK가 있어야 JPA가 매핑할 수 있다.
FK가 반대편에 있으면 누구를 가리키는지 알 수 없어 단방향 매핑이 불가하다.
(양방향은 가능!)
DB관점은 FK를 가진 쪽을 주로 보는 경향이 있고, 도메인 관점은 더 자주 조회되고 관리하는것을 주로 삼기도 한다. 핵심은 FK위치 = 주인결정의 1순위라는 점이다.
연관관계 매핑의 80%는 N:1(다대일) + 주인 = FK쪽 + 필요시 양방향으로 정리된다.
나머지 (1:N, 1:1, N:M)는 쿼리/운영/변경 가능성을 보고 선택해야 한다.
원칙은 단순하다.
FK가 있는 쪽이 주인, 양방향은 탐색 편의! 기억해두자