JPA기본기 다지기_연관관계 매핑(복습..복습..복습....)
JPA 연관관계 매핑
1.객체를 테이블에 맞추어 모델링한다면?
👎🏼👎🏼👎🏼👎🏼
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column
private String name;
private int age;
@Column(name = "TEAM_ID")
private Long teamID;
...
}
@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.setTeamId(team.getId()); // 이 부분!
em.persist(member);
// 조회
Member findMember = em.find(Member.class, member.getId());
//Member와 Team이 연관관계가 없음
Team findTeam = em.find(Team.class, team.getId());
⚠️ Member Entity와 Team Entity를 참조 대신 외래키를 그대로 넣은 경우입니다.
⚠️ 이렇게 하면 조회할 때 연관관계가 없어 2번 조회를 해야합니다.
➡️객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력관계를 만들 수 없습니다.
➡️즉, 객체지향적인 코드가 아닌 의존적인 코드가 됩니다.
👍그럼 객체지향 모델링을 하자!
2.단방향 매핑
Member Entity에서 기존 teamId를 Team team으로 변경
// 맴버 엔티티
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
//@Column(name = "TEAM_ID")
//private Long teamId; // 객체지향스럽지않음! 관계형DB에 맞춘 방식
@ManyToOne
@JoinColumn(name = "TEAM_ID")
**private Team team;** // 객체 지향스러운 방법!
****...
}
// -------------조회-------------
Member member = em.find(Member.class, member.getId);
Team team = member.getTeam();
// -------------수정-------------
Team newTeam = member.getTeam();
member.setTeam(newTeam); // 팀 수정!
➡️이제 member.getTeam을 할 수 있음!
1차 캐시에 있어서 쿼리 날라가는 게 안 보일때? 근데 쿼리를 보고 싶다면!
em.persist(member); // 영속성 컨텍스트에 있음
Team findTeam = em.findMember.getTeam(); //쿼리 안나가고 1차 캐시에서 가져옴
em.flush(); // 그냥 빨리 1차 캐시에 있는 거 강제로 디비에 보내기
em.clear(); // 영속성 컨텍스트(1차캐시) 클리어하면 쿼리는 이제 새롭게 나감!
❓🤔만약에 Team에서도 Member를 조회하고 싶으면 어떡하죠?
3. 양방향 연관관계를 사용하자! 사용할 땐 주인을 따지며 관계를 맺어야한다.
➡️양쪽으로 참조해서 서로 조회할 수 있는 관계
- 테이블의 경우, 원래 방향의 개념이 없다! FK로 받으면 양쪽에서 다 조회가능
- 그러나 객체의 경우, 양쪽에 서로의 객체를 넣어주어야 조회가능!
- 이것 또한 테이블과 객체의 패러다임 차이!
다대일 관계(Member)에는 @ManyToOne만! (단방향과 동일한 방법)
일대다관계(Team)에는 @OneToMany
그리고 컬렉션 추가해주고, mapped by에는 해당 다대일이 매핑되어있는 변수명 넣어주기
//멤버 엔티티
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne // 다대일관계 매핑(단방향 연관관계와 동일한 방법)
@JoinColumn(name = "TEAM_ID")
private Team team;
…
}
//팀 엔티티
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // 일대다관계 매핑
List<Member> members = new ArrayList<Member>();
//(컬렉션 추가해주기, mapped by에는 해당 다대일이 매핑되어있는 변수명!)
…
}
// 한 멤버가 속한 팀의 전체 멤버 리스트도 받아올 수 있음
List<Memeber> members = member.getTeam().getMembers();
mapped by란?
- mapped by 를 제대로 이해하려면 객체-테이블의 연관관계 패러다임 차이를 알아야 한다!
- 객체의 양방향 관계는 사실 서로 다른 단방향 2개를 양방향이라고 부르는 것뿐임
IF ) 멤버가 속한 팀을 바꾸고 싶다면,
팀에서 멤버를 바꿔야될까, 아니면 멤버에서 팀을 바꿔야 될까?
➡️ DB 입장에서는 멤버에서 팀아이디인 FK값만 바꾸면 된다! (자동으로 팀에서 Id값도 바뀌니까)
❓그러면 객체 입장에서는 ??
2개의 단방향 관계로 억지로 만든 양방향관계인데, 무슨 값을 바꿔야 하나?!
➡️그래서 양방향 연관관계의 주인을 정해서, 주인이 FK를 관리하게끔 해보자!
그리고 주인이 아닌 쪽(mapped by로 주인을 걸어줌)은 읽기만 가능하게 하자!
- 주인이 아닌 쪽에는 @OneToMany (mapped by = 주인이름), 읽기(조회)만 가능함
- 주인인 쪽에는 @ManyToOne, 업데이트나 등록 등등은 주인만 참조
그렇다면 누구를 주인으로 할까?
주인은 외래키가 있는 곳으로 정하자 즉, '다'쪽(Member)을 주인으로 하자
반대로 하면 헷갈리고 성능 이슈가 있음!, 쿼리가 반대쪽으로 나가면 헷갈리니까
➡️그냥 쉽게 생각하면, 단방향 매핑에서 다대 "일" 부분에 @OneToMany를 걸어주면 되고,
주인이 아니라는 의미로 mappedBy="매핑되어있는 변수명"을 넣어주면 끝이다!
- @ManyToOne
- fetch
- FetchType.LAZY
- FetchType.EAGER
- 주로 Lazy를 쓰고, 꼭 필요한 곳에서만 EAGER를 씀 (속단해서 최적화하지 말자(?))
- fetch
- @OneToMany
- mappedBy
- "나는 주인이 아니야! 내가 저기에 매핑됐어!"
- mappedBy
- @OneToMany
- @ManyToMany
- 다대다는 일대다, 다대일로 풀어야함
- 현업에선 제약조건이 너무 많아서 잘 안 씀. 걍 일대다, 다대일로 풂
- @JoinColumn, JoinTable
양방향 매핑할 때 주의하기!
: 주인이 아닌쪽에만 매핑하면 매핑이 안됨!
: 객체지향적인 관점에서는 양방향에서 다 매핑해주어야 안전
: 자주 발생하는 오류로는 연관관계 주인이 아닌데 set을 하려고 하는 경우.. 주인이 아니면(=FK를 가지고 있지 않으면)
단순히 get(=조회)밖에 하지 못한다!!!
실습 ) 연관관계 매핑은 주인인 쪽에서는 꼭 해주어야하고, 만약 역방향에서만 하면 설정 안됨
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team); // 이렇게 주인인 쪽에서 연관관계를 설정해주어야함!
// 역방향(주인이 아닌 방향)만 연관관계 설정 => 이것만 하면 설정안됨
team.getMembers().add(member);
em.persist(member);
➡️주인인 곳에서는 수정과, 삭제가 가능하지만 주인이 아닌 방향으로 add를 하려고 하면 안되는 것!
//멤버 엔티티 ('다'쪽, 주인!)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
…
}
//연관관계 편의 메소드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
정리 : 양방향 설계할 때는,
- 처음 설계할 때는 단방향 매핑으로 설계함!
- 필요할 때 양방향은 추가해주면 됨! - 테이블에 영향을 주지 않으니
(역방향에서 조회(주인X쪽에서 주인 조회)하려고 양방향으로 만든 것뿐!)
⇒ 가능하면 단방향으로 하되, 실무에서 양쪽으로 조회가 필요한 경우에 양방향 매핑을 하자!
(양방향은 복잡하니까 필요시에 매핑하자)