자바생
article thumbnail
728x90

연관관계 매핑이 왜 필요할까?

우리가 DB 테이블 구조대로 코드를 작성하면 객체지향적이지 않게 된다.

테이블 구조

위와 같이 테이블 구조대로 코드를 작성하고, 멤버가 속한 팀을 찾아보자.

DB에서 멤버 찾기 -> 멤버의 팀 아이디 찾기 -> 팀 아이디를 통하여 DB에서 팀 찾기

과정이 이뤄진다.

 

하지만 객체지향적으로 코드를 작성하고, 멤버가 속한 팀을 찾아보자.

DB에서 멤버 찾기 -> 멤버의 팀 찾기

과정이 이뤄진다.

과정이 단축되는 것을 알 수 있다.

 

테이블과 객체에서 연관관계를 맺는 데의 차이점이 있다.

테이블은 외래키로 두 테이블이 양방향 관계를 맺게 되고,

객체는 참조를 사용하여 관계(단방향)를 맺는다.

 


단방향 연관관계

현재 위의 테이블을 보면 회원은 하나의 팀에만 소속될 수 있다. 

그래서 Member와 Team은 다대일 관계이다.

 

단방향 연관관계에서는 객체 연관관계와, 어떤 column과 조인하는지 알려줘야한다.

Member는 현재 Team 객체를 참조하고 있다. 그리고 TEAM_ID를 외래키로 가진다.

 

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

@ManyToOne

객체 연관관계를 나타내준다.

현재 Member 와 Team은 다대일 관계로 Membe는 '다', Team은 '일' 이므로 @ManyToOne을 쓴다.

 

@JoinColumn

제일 중요한 점은 어떤 column과 조인하는지 알려줘야한다.

현재 Member에서 외래키를 가지고 있으므로, 어느 외래키로 Team 테이블과 조인을 하는지 알려줘야한다.

@JoinColumn(name = "TEAM_ID")

 


양방향 연관관계

단뱡항 연관관계에서 Member -> Team에만 접근할 수 있었다.

그렇다면 Team -> Member로 접근하는 방법은 없을까?

 

있다.

Team -> Member 일대다 관계이다. 즉, 한 Team에 여러 Member가 들어가있다.

따라서 컬렉션을 사용해야 한다.

 

@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

@OneToMany

Team은 '일', Member는 '다' 로 일대다 구조이다. 따라서 @OneToMany를 사용한다.

 

mappedBy

연관관계를 매핑하면서 어려운 점이 mappedBy라고 한다.

mappedBy를 쉽게 이해하기 위해서는 객체와 테이블 간 연관관계를 맺는 차이를 숙지하고 있어야한다.

 

객체와 테이블 간 연관관계 차이

객체의 양방향 연관관계는 단방향 연관관계가 2개 있다고 생각하자.

그리고 Member에 Team, Team에는 List<Member> members 참조값이 서로 존재한다.

하지만 테이블은 연관관계 한 개로 양방향 가능하다.

 

연관관계의 주인은 왜 필요할까?

양방향 연관관계에서 객체를 양방향으로 만들어놨다. 그렇다면 둘 중에 어떤 걸로 매핑해야할까?

Member에 있는 team 값을 바꿨을 때 MEMBER 테이블의 외래키 값이 업데이트 또는

Team에 있는 members 값이 변했을 때 외래키가 업데이트 되야할까?

 

그래서 룰이 생긴다.

둘 중 하나로 외래 키를 관리해야한다.

Member에 있는 Team 또는 Team에 있는 List members 중에 주인을 정해야한다.

이것이 바로 연관관계의 주인이다.

 

mappedBy라는 뜻 자체가 ~에 의해 매핑되었다라는 말이다.

그니까 mappedBy가 쓰여진 객체는 주인이 아니고, mappedBy = "OOO" OOO이 주인이다.

 

여기서 주인이 아닌 객체는 읽기(조회)만 가능하다. 즉, 해당 객체에 값을 넣어도 아무런 변화가 없다.

왜냐?

DB에 값을 넣거나 업데이트 할 때는 주인인 OOO만 참조하기 때문이다.(자주 헷갈리므로 꼭 기억하자!)

 


양방향 연관관계에서 데이터 저장 시 주의할 점

위에서 말했다시피 양방향 연관관계에서 데이터를 저장할 시, 

주인에게 저장해야 DB에 제대로 저장된다고 했다.

연관관계 주인에게 값을 넣지 않을 경우

위의 사진을 보면 team에 member를 더해도 연관관계가 성립되지 않음을 알 수 있다. (TEAM_ID = null)

연관관계 주인에게 값을 넣을 경우

TEAM_ID에 1이 되있는 것을 알 수 있다.

 


연관관계 편의 메서드

위 사진은 양방향 연관관계에서 데이터를 저장하고 조회가 잘 되는 결과를 볼 수 있다.

하지만 연관관계의 주인에만 값을 저장하면 순수한 객체에서 문제가 발생될 수 있기 때문에,

양쪽 방향에 모두 값을 입력해주는게 좋다고 한다.

 

양족에 넣어주지 않게 되면 2가지 문제가 생길 수 있다.

1. flush, clear가 없을 경우

flush와 clear를 하지 않으면 member와 team은 1차 캐시가 그대로 저장되어 있다.

그래서 쿼리가 나가지 않게 된다

또한, 여기서 중요한 점은 members도 조회되지 않는 점이다.

왜 조회되지 않을까?

findTeam은 1차 캐시에서 그대로 가져온 것이다.

즉, 연관관계를 매핑하지 않고 처음 team을 영속성 컨텍스트에 넣을 때 그 시점의 team이다. 

따라서 team은 메모리에서 그대로 가져온, 순수한 객체 상태이기 때문에 컬렉션에도 값이 있지 않게 된다.

 

2. 양쪽에 값을 넣어줬을 때, //** 중에 한 개를 까먹을 수 있다.

까먹는 것을 방지하기 위해 둘을 하나의 메서드로 만들어 실수를 방지한다.

이것을 연관관계 편의 메서드라 한다.


연관관계 편의 메서드 중 변경 시 주의할 점

연관관계 편의 메서드를 작성하고, 연관관계를 변경할 시 주의할 점이 있다.

 

member1.setTeam(teamA);
member1.setTeam(teamB);

Member findMember = teamA.getMember();

findMember는 여전히 member1이 조회되는 것을 알 수 있다.

 

따라서 setTeam을 잘 작성하여 위와 같은 오류가 생기지 않게 해야한다.

 


양방향 연관관계 시 무한 루프 

양방향 연관관계를 사용하다가 무한 루프에 걸릴 수 있다.

 

아무 생각 없이 양방향 연관관계를 맺고 있는 두 클래스에 toString lombok을 사용하게 되면

아래의 빨간 줄이 계속해서 참조가 되어 무한루프에 빠질 수 있다.

 

 

두 번째로 JSON 생성 라이브러리 무한 루프가 있다.

 

controller에서 entity를 response로 보낼 때, 두 entity가 양방향이면 무한루프가 발생한다.

entity를 JSON으로 바꾸면 team이 있고, team에서 JSON을 또 바꿀 때 members가 있기 때문에 무한루프가 발생한다.

또한, entity가 변경되는 순간에 api 스펙이 바뀌기 때문에 혼란스러워질 수 있다.

따라서 controller에서 entity를 직접 절대 반환하지 않고, DTO를 사용하자.

 


Reference

 

 

 

- 자바 ORM 표준 JPA 프로그래밍 (김영한 지음)

 

 

 

 

728x90

'Spring 강의 > JPA - 기본편' 카테고리의 다른 글

고급 매핑  (0) 2021.12.07
다양한 연관관계 매핑  (0) 2021.12.02
앤티티 매핑  (0) 2021.11.20
영속성 관리 - 내부 동작 방식  (0) 2021.11.19
JPA 시작하기  (0) 2021.11.17
profile

자바생

@자바생

틀린 부분이 있다면 댓글 부탁드립니다~😀

검색 태그