본 포스팅은 인프런 - 자바 ORM 표준 JPA 프로그래밍 (기본편) 을 강의를 바탕으로 공부하고 정리한 글입니다.
양방향 매핑
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "user_name")
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
- Member 엔티티는 단방향 매핑과 동일하다.
- Team 엔티티에 List<Member>컬렉션을 추가한다.
- mappedBy를 통해 연관관계 주인을 지정해준다.
// 조회
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
List<Member> members = findTeam.getMember(); // 역방향 조회
- Team에서 Member로 반대 방향의 객체 그래프 탐색이 가능하다.
객체와 테이블이 관계를 맺는 차이
객체의 양방향 연관관계
- 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
- 회원 → 팀 연관관계 1개 (단방향)
- 팀 → 회원 연관관계 1개 (단방향)
- 따라서 객체를 양방향으로 참조하려면 단방향 관계 2개를 만들어야 한다.
- A → B (a.getB())
- B → A (b.getA())
class A {
B b;
}
class B {
A a;
}
테이블의 양방향 연관관계
- 테이블은 외래 키(FK) 하나로 두 테이블의 연관관계를 관리한다.
- 회원 ↔ 팀 연관관계 1개 (양방향)
- MEMBER.TEAM_ID 외래키 하나로 양방향 연관관계를 가지는 것이다. → 양쪽으로 조인 가능
-- 회원의 팀을 알고 싶은 경우
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
-- 팀에 속하는 회원들을 알고 싶은 경우
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID
정리
이렇게 객체와 테이블이 관계를 맺는 차이가 있기 때문에 둘 중 하나로 외래 키를 관리해야 한다.
예를 들어 Member를 바꾸고 싶다거나, 새로운 Team에 들어가고 싶다면?
- 객체 : Member의 team 값을 변경해야 할까, Team의 members를 바꿔야 할까?
- 테이블 : MEMBER.TEAM_ID 외래키 값만 변경되면 됨.
- 이 둘의 차이가 있기 때문에 둘 중 하나로 주인을 정해 외래키를 관리해야 하는 것이다.
따라서 외래 키를 관리 해줄 연관관계의 주인을 정해야 한다.
연관관계 주인(Owner)
양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래 키를 관리할 수 있다. (등록 및 수정)
- 주인이 아닌쪽은 읽기만 가능하다.
- 주인이 아니면 mappedBy(name = "") 속성으로 주인을 지정해줘야 한다.
누구를 주인으로 해야하지?
- 외래 키가 있는 곳을 주인으로 정하자.
- 1:N이라면 N(@ManyToOne)인 곳
- ex) Member.team이 연관관계의 주인이 된다.
양방향 매핑 시 주의점
양방향 매핑 시 연관관계의 주인에 값을 입력하지 않아 문제를 겪는 경우가 많다.
어떤 상황인지 예제 코드를 통해 알아보자.
Member member = new Member();
member.setUsername("anne");
em.persist(member);
Team team = new Team();
team.setName("팀A");
// 역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(team);
연관관계 주인이 아닌 Team쪽에서 Member를 추가해줬다.
이 경우 DB를 실제로 확인해보면 Member의 Team의 외래키 값이 null로 저장된다 .
연관관계 주인인 Member쪽에서 Team을 설정하면 어떻게 될까?
Team team = new Team();
team.setName("팀A");
em.persist(team);
Member member = new Member();
member.setUsername("anne");
// 연관관계 주인에서 연관관계 설정
member.setTeam(team);
em.persist(member);
null이 아닌 Team의 외래키가 잘 저장된 것을 확인할 수 있다.
✅ 정리
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 연관관계 주인에 값 설정
team.getMembers().add(member); // 연관관계 주인이 아닌 곳에도 값 설정
em.persist(member);
- 반드시 연관관계의 주인에 값을 입력해야 한다.
- 순수 객체 상태를 고려해 항상 양쪽 모두 값을 설정해야 한다.
- 이를 위해 연관관계 편의 메소드 활용하면 좋다.(아래에서 설명)
- 양방향 매핑 시 무한 루프에 조심해야 한다.
- toString(), lombok, JSON 생성 라이브러리
연관관계 편의 메소드
앞서 알아봤듯이 양방향 매핑시 연관관계 주인과 연관관계 주인이 아닌 곳 모두 값을 설정해줘야 한다.
개발을 하다보면 하나를 빼먹는 실수를 할 수 있기 때문에 연관관계 편의 메소드라는 것을 생성해 사용하는 경우가 많다.
연관관계 편의 메소드는 1:N에서 1쪽에 지정해도 되고, N쪽에 지정해도 무관하다.
방향은 상관 없지만 연관관계 편의 메소드가 양쪽에 있으면 문제를 일으킬 수 있기 때문에 한쪽에만 만들어야 한다.
👉🏻 N(Many)쪽에 지정
@Entity
public class Member {
...
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
/**
* 연관관계 편의 메소드
*/
public void changeTeam(Team team) {
this.team = team;
team.getMember().add(this);
}
}
👉🏻 1(One)쪽에 지정
@Entity
public class Team {
...
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
/**
* 연관관계 편의 메소드
*/
public void addMember(Member member) {
member.setTeam(this);
members.add(member);
}
}
'🌱 Spring > JPA' 카테고리의 다른 글
[JPA] 즉시 로딩(LAZY)과 지연 로딩(EAGER) (0) | 2022.10.18 |
---|---|
[JPA] 프록시 (0) | 2022.10.18 |
[JPA] 상속관계 매핑(@Inheritance), 매핑 정보 상속@MappedSuperclass) (0) | 2022.10.17 |
[JPA] 연관관계 매핑(3) - 다중성 (ManyToOne, ManyToOne, OneToOne, ManyToMany) (1) | 2022.10.11 |
[JPA] 연관관계 매핑(1) : 단방향 매핑 (0) | 2022.10.06 |
[JPA] 데이터베이스 스키마 자동 생성 : DDL AUTO (0) | 2022.09.30 |
[JPA] 엔티티 매핑(2) - 기본 키 매핑 전략 (1) | 2022.09.30 |
[JPA] 엔티티 매핑(1) - 객체와 테이블, 필드와 컬럼 매핑 (0) | 2022.09.30 |