본문 바로가기

TIL & WIL

TIL_220609_JPA, 단방향 연관관계 + 소감~?(양방향은 다음편)

주특기 심화 주차가 끝나는 날이다.(and 부트캠프 32일차)

이번 주가 가장 생각할 것이 많았던 주였던 것 같다.

개인적인 커리어 패스의 변화 결정도 그렇고,

심화 주차에 새로 배우게 된 개념들도 그렇고...ㅋㅋ

꾸준히 TIL을 정리를 해야겠다는 생각은 항상 있었지만,

그 실행을 옮기기까지 시간이 좀 걸렸던 것 같다 ㅎㅎ

오늘부터는 간단한 것들이라도 끄적여봐야지.

 

우선은 개념이해를 위해 JPA 연관관계에 대해 강의를 하신 김영한님의 유튜브 강연을 두 개 보았었다.

연관관계 매핑, 단방향, 양방향 등에 대해서 말씀하시긴 했는데 아무래도 실제로 오프라인에서 진행된 강연을

녹화한 영상이다보니 속 시원히 이해가 안되는 부분이 있어 구글링을 했는데,

김영한님의 강의와 책을 기반으로 글을 잘 써주신 블로거가 있어서 하나하나 뜯어보며 개념정리를 했다.

 

<참고 블로그>

https://velog.io/@conatuseus/%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91-%EA%B8%B0%EC%B4%88-1-i3k0xuve9i

 

[JPA] 연관관계 매핑 기초 #1 (연관관계의 필요성, 단방향 연관관계)

이번 글에서는 **에 대해 알아보겠습니다. 이 시리즈 글은 김영한 님의 강의, 책을 보고 적은 것임을 알려드립니다. (강추) 1. 연관관계가 필요한 이유 > > - 회원과 팀이 있다. > - 회원은 하나의 팀

velog.io

 

우선 잘 정리해주신 블로거 분께 감사의 말씀을 드리며,

해당 블로그를 기준으로 이번 주차에 진행했던 연관관계에 대해 써보고자 한다.

 

위 이미지에서 보면 Member 엔티티(Class)에 teamId가 멤버 변수로 들어와있고,

테이블에서는 TEAM_IDFK(Foreign Key, 외래키) 값으로 연관이 되어 서로를 참조할 수 있게 되어있다.

 

@Entity
public class Member {

  @Id @GeneratedValue
  private Long id;

  @Column(name = "USERNAME") 
  private String name;

  @Column(name = "TEAM_ID")
  private Long teamId;
  ...
  }

@Entity
public class Team {

  @Id @GeneratedValue
  private Long id;
  private String name;
  ...
  }

우선 JPA 연관관계 매핑이 없이 각각의 엔티티를 연관 있게 만들려면,

Member 클래스에 teamId를 멤버변수로 선언해 줘야 함을 알 수 있다.

 

만약 회원이 속한 팀을 찾으려면, member 객체를 가져와서 > 거기서 teamId를 꺼내고 > 해당 teamId에 해당하는 team 객체를 찾아와야하는 3번의 지루한 과정이 필요함을 알 수 있다!

 

저번 프로젝트 시에 했던 과정을 돌이켜보면 아마도

특정 게시글과 그 게시글과 연관되어있는 댓글의 관계를 떠올려 볼 수 있겠다.

댓글 테이블에는 PostNum(상세 게시글 번호)가 함께 저장이 되어있었으니까 말이다. 

그때는 연관관계 테이블을 설정하는 방법 자체를 몰랐기 때문에

댓글을 쓸 때마다 클라이언트 측에서 Ajax 통신으로 넘어온 PostNum을 RequestDto로 받아서

Repository를 이용하여 DB에 저장했던 기억이 난다.

 

이럴 때 이번에 배운 객체 간의 단방향 매핑관계를 사용한다면

더 손쉽게 DB 테이블에 저장할 수 있지 않을까 하는 생각이 든다.

상세게시글 1개 당 여러개의 댓글이 달릴 수 있으니까 말이다.(1:N의 관계)

 


 

@Entity
public class Member {

  @Id @GeneratedValue
  private Long id;

  @Column(name = "USERNAME") 
  private String name;

  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;
  
  // Getter, Setter ...
}

@Entity
public class Team {

  @Id @GeneratedValue
  private Long id;
  private String name;
  
  // Getter, Setter ...
}

 

해당 이미지와 코드가 위쪽에서 봤던 코드와 다른 점은 Member 클래스의 멤버변수로 teamId가 들어가는 것이 아니라,

team 객체 자체가 들어간다는 점이다.

 

이렇게되면 회원의 팀을 찾고싶을 때,

1) member객체를 불러오고

2) member.getTeam() 과 같은 형식으로 참조를 사용하여 연관관계를 조회할 수 있다.

 

위에서 봤던 3번의 과정보다 훨씬 수고로움이 덜어진 것을 볼 수 있다!

 

특히 해당 Entity의 Column을 선언할 때 쓰는 @ManyToOne, @JoinColumn 어노테이션에 주목하자.

ManyToOne이라는 것은 Member(여러명, Many) : Team(1개, One)의 관계가 성립하기 때문에

Member 엔티티에 Join하고자하는 컬럼 위에 @ManyToOne을 기입하는 것이다.

 

그리고 JoinColumn(name="TEAM_ID")은 TEAM_ID(외래키)를 통해

상대방 즉, 참조 당하는 객체(Referencing Side)의 Primary_Key를 컬럼으로 갖게 된다.

 

여기서 한 가지 더 알고가야 할 개념은 "객체 연관관계"와 "테이블 연관관계"의 차이점이다.(위 이미지 참고)

 


 

<객체 연관관계>

  • 회원 객체(Member)는 Member.team 멤버변수로 팀 객체(Team)와 연관관계를 맺는다.
  • 회원 객체와 팀 객체는 '단방향 연관관계'를 맺는다.
  • 회원은 Member.getTeam()을 통해 팀을 알 수 있지만, 팀에서는 소속된 회원이 누군지 조회할 수 없다.

<테이블 연관관계>

  • 회원 테이블은 TEAM_ID(FK)로 팀 테이블과 연관관계를 맺는다.
  • 회원 테이블과 팀 테이블은 '양방향 연관관계'를 가진다.
  • 회원 테이블의 TEAM_ID(외래키)를 통해 팀 테이블과 조인할 수 있고, 
  • 반대로 팀 테이블도 회원 테이블과 TEAM_ID를 통해 조인할 수 있다.

- 회원과 팀을 조인하는 SQL

SELECT * FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

- 팀과 회원을 조인하는 SQL

SELECT * FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID

 

정리하자면,

 

객체 참조를 통한 연관관계는 언제나 단방향이라는 것이다. 객체 간의 양방향 연관관계를 만들려면,

반대쪽 객체에도 컬럼을 추가하여 참조를 보관해야 한다는 것이다. 정확히 말하면 이는 양방향이라고 표현은 하지만

단방향 관계 2개가 서로를 바라보는 것이라고 볼 수 있다.

그에 반해 테이블은 외래키(FK) 1개로 양방향 연관관계를 맺으며 해당 외래키로 서로가 서로를 JOIN 할 수 있다.

 

 

다음 글에서는 양방향 매핑과 연관관계의 주인 등에 대해 써봐야겠다.

오늘은 일찍자자. 뿅.