JPA

JPA기본기 다지기_연관관계 매핑(복습..복습..복습....)

JANNNNNN 2024. 3. 23. 15:47

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="매핑되어있는 변수명"을 넣어주면 끝이다!


매핑 Annotation

  • @ManyToOne
    • fetch
      • FetchType.LAZY
      • FetchType.EAGER
      • 주로 Lazy를 쓰고, 꼭 필요한 곳에서만 EAGER를 씀 (속단해서 최적화하지 말자(?))
  • @OneToMany
    • 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); 
}

 


정리 : 양방향 설계할 때는,

  1. 처음 설계할 때는 단방향 매핑으로 설계함!
  2. 필요할 때 양방향은 추가해주면 됨! - 테이블에 영향을 주지 않으니
    (역방향에서 조회(주인X쪽에서 주인 조회)하려고 양방향으로 만든 것뿐!)

⇒ 가능하면 단방향으로 하되, 실무에서 양쪽으로 조회가 필요한 경우에 양방향 매핑을 하자!
(
양방향은 복잡하니까 필요시에 매핑하자)