본문 바로가기

스프링/JPA

JPA - 양방향 연관관계에서 '연관관계의 주인' Mappedby

JPA는

과거 RDBMS 의존적인 객체지향 프로그래밍 방식에서 탈피하기위해 고안된 방법이다.

테이블, 테이블의 관계를 객체와 객체의 관계로 표현하는 방법이다.

 

테이블 -> 객체, 테이블 연관관계 -> 객체 연관관계로 단순 치환하게 되면 문제가 발생할 수 밖에 없다.

 

 

 

김영한님이 곧 잘 들어주시는 예시다

멤버는 자신이 속한 팀에대한 정보를,

팀은 자신의 팀에 대한 정보를,

각각 멤버 변수로 가지고 있다.

그리고 양방향 객체 관계라고 되어있지만, 사실은 멤버는 팀을, 팀은 멤버를 바라보는 단방향 연관관계 두개라고 보면된다.

 

여기서 패러다임의 차이가 발생한다.

 

테이블의 경우에는 TEAM은 굳이 members를 가지고 있을 필요가 없다.

조인을 걸어서 조건으로 그냥 MEMBER를 구할 수 있고, 그게 RDBMS의 장점이기 때문이다. (정규화)

그렇기 때문에 RDBMS에서는 양방향 연관관계라는 개념이 필요하지가 않다.

 

그렇다면, JPA는 데이터베이스의 기본 원칙도 못지키는 뒤떨어진 패러다임인가?

그건 아닌게 결국 JPA가 내부적으로 아래의 테이블처럼 스키마를 만들어 준다.

 

하지만 JPA에서 문제가 발생하는게, 

멤버 객체의 TEAM 정보를 바꾸고 싶을 때 발생한다.

 

RDBMS의 경우에는 그냥 MEMBER 테이블에 TEAM_ID 컬럼 값만 바꿔주면 TEAM은 건드릴 이유도 필요도 없다.

근데 JPA는 MEMBER 객체의 TEAM 멤버 변수의 값을 바꾼다고 해서 TEAM의 MEMBERS 변수의 값이 변한다고 장담할수 없는 상황이 연출된다. 

 

이를 해결하기 위해서 JPA에서 규칙이 생겨난다.(사실 약속이기 때문에 왜 다른 방법도 있는데?라고 하면 할말 없다.)

 

1. 객체의 두 관계 중 하나를 연관관계의 주인으로 지정

(이 경우에는 당연히 MEMBER가 된다.)

 

2. 연관관계의 주인만이 외래키를 관리한다.

 

3. 주인이 아닌쪽은 읽기만 가능

(예를들어 TEAM에서 MEMBERS에 변화를 줘도 MEMBERS는 변하지않음

TEAM.setMember() ~ 해봤자, RDBMS에는 member부분에는 null이 뜸 저장안됨)

 

4. 주인은 mappedBy 속성 사용X

 

5. 주인이 아니면 mappedby 속성으로 주인을 지정해줘야한다.

 

 

그래서, 연관관계 주인은 어떻게 정할건데?

이걸 보면 명확해진다.

N:M 연관관계는 N:1 M:1의 연관관계로 나눌 수 있으니 제외하고,

 

1:1 연관관계, 1:N 연관관계는 위처럼 결국 외래키를 갖는 형태로 정규화 할 수 있다.

결국 위의 원칙대로 외래키를 가진 테이블이 연관관계의 주인이 되어야 한다.

1:N의 경우에는 N이 주인이고 1의 외래키(사실은 없는 키인데 객체 지향원칙에 의해, JPA에 의해 생겨남)에

Mappedby(주인객체의 외래키 멤버변수명)가 붙는다.

 

결론을 내자면

연관관계 주인이라는 개념은 ORM패러다임의 한계를 극복하기 위해 만들어진 개념이고 외래키(RDBMS상를 갖는 객체가 연관관계의 주인이된다.

 

 

퀴즈를 통해 고수가 되자.

 

이건 또 다른 스프링고수 백기선님의 강의에 나온 예제인데, 이 코드에서 잘못된 점을 찾아보자

 

이상태에서

 

Bookstrore bookstore = new Bookstore()

bookstore.set~

~

Book book = new Book()

book.set ~ 

~

 

bookstore.add(book)하면

book을 조회했을 때 bookstore가 등록될까?

 

정답은 NO.. 

 

이유는 연관관계 주인에서 찾을 수 있다.

처음에도 말했다 싶이 결국 JPA또 RDBMS적으로 봤을 때는 FK가 두개가 있는게 아니라 한개밖에 없다.

bookstore에 있는 books들은 사실상 객체지향에서 의미를 갖는거지 실제 RDBMS상에서는 아무런 의미가 없다.

그래서 NULL이 뜨는 것.

 

원칙적으로 이를 해결하기위해서

add 메소드에  book.setBookstore(this) 를 한 줄 추가해줌으로 써 해결할 수 있다.

book이나 bookstore에 무엇인가를 추가할 일이 있다면,

book에도 해당 bookstore를, bookstore에도 book을 추가해주는 방식을 사용하면 되는 것...

 

그러면 두번 저장되는거아니냐라는 의문이 남을 수 있다.

 

이 부분은 JPA의 내부 코드를 확인해보면 알 수 있는데 이 부분은 앞으로 더 공부해나가면서 추가해보도록 하겠다.

 

그리고, 연관관계 메서드를 설정해둠으로써

연관관계의 모호함을 없애기 위해서

set메서드를 통해서 연관관계주인과 mapped된 멤버변수를 하나의 메서드를 묶어서 관리하면 편하다.

public void setBookStore(BookStore bookstore){
	this.bookstore = bookstore
    bookstore.getBooks().add(this) // 이 book에도 bookstore정보를 저장 -> 원자화
}

'스프링 > JPA' 카테고리의 다른 글

JPA 기본 시리즈 - 1. 영속성 관리  (0) 2022.01.13