목차
목표 : "객체의 참조와 테이블의 외래키를 매핑"
시나리오
- 회원과 팀은 다대일 관계이다.
- N명의 회원은 1개의 팀에 소속될 수 있다.
- 1개의 팀은 N명의 회원을 가질 수 있다.
객체를 테이블에 맞추어 모델링 : 외래키를 그대로 객체에 구현
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId()); //⭐⭐⭐ 식별자 저장
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
Team findTeam = em.find(Team.class, team.getId()); //⭐⭐⭐ 식별자 조회
데이터 중심 모델링의 문제점
테이블은 외래키로 조인을 사용하여 연관된 테이블을 찾는다.
하지만 객체는? 참조를 사용하여 연관된 객체를 찾는다.
=> 어디에..맞추지?!
객체 지향 모델링 : 객체 연관관계 사용
JPA에게 객체 간 연관관계를 알려주어서 테이블에도 연관관계가 적용되도록 해야한다.
단방향 매핑
1. 연관관계 파악 : 누가 1이고 누가 N인가?
Team이 1이고 Member가 N이다.
Team테이블에 PK가 있고 Member테이블에 FK가 있다.
2. N에 @ManyToOne, @JoinColumn을 작성한다
@Entity
public class Member{
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne //⭐⭐⭐ Member가 N(many), Team이 1(one)이다.
@JoinColumn(name = "team_id") //⭐⭐⭐ team_id로 FK 설정한다.
private Team team;
}
@Entity
public class Team{
@Id @GeneratedValue
private Long id;
private String name;
}
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team); //⭐⭐⭐ 참조 저장
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
Team findTeam = fiindMember.getTeam(); //⭐⭐⭐ 참조 조회
양방향 매핑
1. 연관관계 파악 : 누가 1이고 누가 N인가?
Team이 1이고 Member가 N이다.
Team테이블에 PK가 있고 Member테이블에 FK가 있다.
Team객체는 List<Member>가 있고 Member객체는 Team이 있다.
Team이 상대(Member)를 N개 가져올 수 있고, Member는 상대(Team)을 1개 가져올 수 있다.
2. N에 @ManyToOne, @JoinColumn을 작성한다
1에 @OneToMany(mappedBy="~")를 작성한다.
@Entity
public class Member{
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne //⭐⭐⭐ Member가 N(many), Team이 1(one)이다.
@JoinColumn(name = "team_id") //⭐⭐⭐ team_id로 FK 설정한다.
private Team team;
}
@Entity
public class Team{
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") //⭐⭐⭐ Team이 1(one), Member가 N(many)이다.
private List<Member> members = new ArrayList<>(); //add할때 NullPointerException안뜨게!
//mappedBy에는 One에 해당되는 객체의 어떤 필드와 연관되어있는지 작성
}
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team); //⭐⭐⭐ 참조 저장
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
Team findTeam = fiindMember.getTeam(); //⭐⭐⭐ 참조 조회
List<Member> members = findTeam.getMembers(); //⭐⭐⭐ 참조 조회
연관관계의 주인
객체의 연관관계에는 방향이 있지만, 테이블의 연관관계에는 방향이 없다!
양방향 매핑의 경우를 생각해보자.
객체 | 테이블 |
Member → Team 단방향 1개 Member ← Team 단방향 1개 |
Member ↔ Team 양방향 1개 |
객체의 양방향관계는 서로 다른 단방향관계 2개이다.
테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다. (조인)
연관관계의 주인 : 테이블에서 외래키가 있는 곳을 주인으로 하자
양방향 매핑 규칙
- 두 객체 중 하나만 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래키를 관리(등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능
- 주인이 아닌쪽이 mappedBy속성을 사용한다. (이미 누군가에 의해 연관이 되어져있다.)
테이블에서 외래키가 있는 곳의 반대를 주인으로하면??
여기서 Team을 주인으로 한다고 생각해보자.
List<Member>를 수정하면 Team에 쿼리가 나가는 것이 아닌 Member에 쿼리가 나가게된다. (수정 주체는 Team인데..)
양방향 매핑 시 가장 많이 하는 실수!
연관관계의 주인이 아닌 곳에 값을 넣는 것
현재 Member가 연관관계의 주인이고, Team은 주인이 아니다.
Member member = new Member();
member.setName("member1");
//member.setTeam(team); //⭐⭐⭐ 주석처리
em.persist(member);
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member); //⭐⭐⭐ 참조저장
em.persist(team);
주인이 아닌 곳(Team)에 Member를 추가했다.
위 코드의 결과는?
Team에 null이 들어갔다.
주인이 아닌 곳(Team)에 Member를 추가해도 DB에 반영되지 않는다.
Team team = new Team();
team.setName("TeamA");
//team.getMembers().add(member); //⭐⭐⭐ 주석처리
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team); //⭐⭐⭐ 참조저장
em.persist(member);
주인인 곳(Member)에 Team을 추가했다.
위 코드의 결과는?
값이 정상적으로 들어갔다.
(..일단 헷갈린다.)
객체입장에서 생각해보자.
Member입장에서는 setTeam(team)을 해주어야 Team값이 입력되고
Team입장에서는 add(member)을 해주어야 Member가 리스트에 저장된다.
결론 : 양방향 매핑은 연관관계의 주인이던 아니던 둘다 값을 넣어주자!!
아니면, 연관관계의 주인인 Member의 setTeam()메서드를 아래와 같이 수정하자
(아예 리스트에 넣어주는 로직까지 추가!)
public class Member{
public void setTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}
양방향 매핑 시 무한루프 조심!
- toString()
public class Member{
@Override
public String toString(){
return "Member{" + "id=" + id + ", username=" + username + ", team=" + team + "}";
}
}
public class Team{
@Override
public String toString(){
return "Team{" + "id=" + id + ", name=" + name + ", members=" + members + "}";
}
}
Member의 team에서 Team을 호출
Team의 members에서 다시 리스트의 크기만큼 Member호출....
컨트롤러에서 Entity를 JSON으로 바꿔서 그대로 응답해주게되면
JSON생성 라이브러리가 마찬가지로 계속 무한루프로 호출하게된다!
정리
- 되도록 단방향 매핑으로 연관관계 매핑을 끝내자
- 양방향 매핑은 객체 그래프 탐색기능이 추가된 것 뿐이다.
- 정말 필요한 경우에만! 양방향 매핑을 걸어주자
참고 : 자바 ORM 표준 JPA 프로그래밍 - 기본편
'Backend' 카테고리의 다른 글
[JPA] 다양한 연관관계 매핑(다대일, 일대일, 다대다) (0) | 2022.02.21 |
---|---|
[JPA]영속성 관리 : 내부 동작 방식 (0) | 2022.02.21 |
[OAuth2] 동작과정 (0) | 2022.02.05 |
[Spring Security] OAuth 네이버 로그인하기 (10) | 2022.02.02 |
[Spring Security] OAuth 구글 로그인하기 (0) | 2022.02.02 |