자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 5. 연관관계 매핑 기초
CS/김영한 스프링 강의

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 5. 연관관계 매핑 기초

전 마지막에 id로 해서 가져와 데이터 지향이 되서 결국엔 객체지향적으로 못하는 문제가 있었는데 이를 해결해본다.

그 전에 사실 객체지향 자체에 대해 이해하는게 더 중요함. 이걸 이해하는데 시간이 더 들거다..

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=60550259 

 

객체지향의 사실과 오해

위키북스 IT Leaders 시리즈 23권. 객체지향이란 무엇인가? 이 책은 이 질문에 대한 답을 찾기 위해 노력하고 있는 모든 개발자를 위한 책이다.

www.aladin.co.kr

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=193681076 

 

오브젝트

역할, 책임, 협력에 기반해 객체지향 프로그램을 설계하고 구현하는 방법, 응집도와 결합도를 이용해 설계를 트레이드오프하는 방법, 설계를 유연하게 만드는 다양한 의존성 관리 기법, 타입 계

www.aladin.co.kr

 

 

시나리오로 팀 안에 멤버 여러개가 있는 경우. 모델링 하고 실제 코드를 짜보면 아래와 같이 된다.

이렇게 각자의 테이블 연관관계를 fk로만 가지고 연관짓는다. db설계상 아무 문제 없고 이게 정석이다. 앞으로도 이렇게 사용하지만 join같은걸 자동으로 해주는 것일 뿐임.

어쨌든 연관관계가 없는 이 상태로 멤버가 어떤 팀에 속하는지를 보는 코드를 짜려면 id를 찾아서 find를 해줘야 하는 번거로움이 있다. 데이터 지향적이라 이러는 것.

 

사실상 join을 자바 코드로 하는 셈인데, 이걸 엔티티의 @JoinColumn을 사용하여 join을 어떤 column 기준으로 할지 정해주고 그냥 객체 가져오듯이 하면 jpa가 알아서 join하는 코드 보내와 정보를 받아온다.

 

단방향이라 얘는 안바뀌어도 됨. 변화없음

 

1차 캐시에 들어있으니 db와 통신 없이 그냥 캐시에서 가져와 sql문을 작성 안함. flush, clear한 뒤 해보면 진짜 한다.

 

 

 

이 밑에부터 진짜 중요한데 정말 어려운 개념이다. 양방향 연관관계와 연관관계의 주인

양방향 매핑을 볼건데, 이 예제에서 team은 그냥 자기 아이디와 이름만 있을 뿐 아무것도 없고 member에서 team을 외래키로 가지고 있을 뿐이었다. 즉 member에만 team에 대한 정보가 있다는 소리. 그래서 team에서도 member에 대해 접근하고 싶으면 현재 다대일 관계이니 List로 해서 변수 정의하면 가져올 수 있다.

이렇게 team에서도 list를 통해 member정보를 정의할 수 있지만 db는 아무것도 달라진게 없다. 여기서 괴리가 발생한다.

사실 db에서는 단방향 양방향 이런 개념이 없다. 그냥 연결 하면 연결 되는거고 아님 마는거고 하는 것. 즉 양방향이네 뭐네 하는건 자바 객체 관점에서 알기 쉽게 접근 정리 개념을 정의하려고 그런거지 db는 그냥 외래키를 하나라도 가지고 있으면 join을 양쪽에서 동시에 가능하다.

 

 

실제로 자바에서 객체 매핑(연관관계 매핑)을 하든 말든 데이터 중심 접근을 했던 것 처럼 아이디를 똑같이 가지고 있을 뿐이고, 편리 기능으로 @JoinColumn으로 알아서 join하도록 했을 뿐이다. 그래서 이름을 어디랑 매핑할 지를 정해야 했던거고.

 

이따 설명하겠지만 실제로 team에서 member를 참조하기 위한 변수를 생성한 뒤 주인을 정해두면 db상에선 아무일도 일어나지 않는다. 참고로 mappedBy가 없으면 db가 정말 양쪽에서 수정이 가능해야 해서 이를 위한 테이블이 하나 생성됨.

 

 

그럼 mappedBy를 정의해야 하는 이유를 보면, 분명 member에서만 team에 대한 정보를 알고 싶어했는데 자바 객체 편의성을 위해 team에서도 member 객체를 바로 가져올 수 있다. 그럼 member에서도 team을 수정할 수 있고, team에서도 list에서 member 하나 꺼내서 변경할 수 있는 가능성이 생긴다. 그래서 db는 어디를 보고 변경해야 되는지 모르는 문제점이 생긴다.

 

그래서 db를 변경하는 애를 하나로만 가능하게 하자고 주인 개념을 만들었다. 주인이 아닌 쪽은 mappedBy로 주인이 자신을 어떤 변수명으로 보는지 적어준다.

jpa를 진짜로 잘하는 사람은 어떤 객체를 주인으로 할 것인지 괜찮지만, 추천하는 방식은 왜래키를 가지고 있는 애를 주인으로 할 것. 이유는 키를 직접 가지고 있어 직관적인 것도 있지만 외래키를 가지고 있는 애가 보통 ManyToOne에서 Many쪽이기 때문도 있다.

 

일단 이렇게 양방향 매핑을 만들었다고 치자. 그럼 자주 하는 실수가 있는데, mappedBy는 읽기 전용이라 db에 반영이 안되서 set을 하려고 해도 안된다. 그래서 주인객체에다가는 안하고 읽기용 변수에만 set을 해서 왜 없지?? 하는 것.

mappedBy해서 읽기 전용인 team에서 member를 불러와 team을 집어넣고 있다.

안된다.

 

그래서 주인 객체에다가만 넣어도 db에 반영된걸 볼 수 있다.

하지만 사실 여기에도 함정이 있다. db로만 보면 문제가 안되지만 이건 객체적으로 맞지 않다는 거다.

이게 무슨 말이냐면 member는 team을 정해서 team이 있는데, 정작 그 team 객체에선 list에 그 member를 포함하지 않아 없다. 하지만 db에는 반영되고 EntityManager는 db만 보고 이런거 관계없이 그냥 join을 하기 때문에 조회할 땐 들어있는거. 그리고 순수 객체로만 봐도 문제가 있다.

그래서 db에 바로 업데이트 안하고 1차 캐시에 저장해서 sql문 모아놓았다가 적용하는 방식에서 문제가 발생한다. 1차 캐시는 실제 자바 list 딕셔너리 쓰는거라 없으면 member는 team이 있는데 team의 member 리스트에는 없는 문제가 진짜로 발생한다. 위에서 되었던건 flush, clear해서 있는것.

 

그래서 결론은 양방향을 쓰려면 읽기전용 상관없이 둘 다에 적용해야 한다.

 

 

 

근데 매번 둘다 이렇게 하기가 힘드니 주인 객체든, 읽기 전용 객체든 회사의 방침으로 한 쪽을 정해서 함수를 만들어서 하는걸 추천

 

 

 

toString에 무한 루프가 무슨말이냐면, 양방향을 했기 때문에 각자 객체가 정보를 가지고 있어 toString을 서로 양쪽 객체에서 하려는 현상.

 

이런 문제는 컨트롤러에서 절대로 엔티티를 그대로 반환하지 않고 DTO를 만들어서 JSON으로 내보내는 방식으로 하면 대부분 해결된다.

 

 

.. 사실 이렇게 양방향 시에 주의점들을 얘기했지만 양방향을 안 쓰는게 제일 좋다. 이미 단방향 매핑 만으로도 연관관계 매핑은 끝나기 때문.

 

그래서 처음 설계할 땐 단방향으로만 테이블 설계를 해서 끝내놓는다. 양방향은 나중에 필요할 때 추가하는 방식으로 한다. 양방향은 자바 객체 관점에서도 별로 좋은 방법은 아니다...

또 비즈니스 관점이 아니라 그냥 외래키를 가지고 있는 애를 매핑 주인으로 하면 고민이 필요없다. 대부분 맞는 듯.

 

 

이제 실습 하던거에서 추가하자. 테이블 구조는 똑같지만 객체에서 참조를 하도록 변경한다.

 

 

일단 단방향으로 설계하기 위해 Order, OrderItem부터 수정하자.

 

이제 양방향으로 Member에서 orders를 보도록 할 텐데..

사실 좋은 설계 방식은 아니다. 이미 order에서 member를 가지고 있으니 어떤 member가 주문을 어떻게 하였는지는 order를 통해서 join하여 정보를 가져오는게 좋지, member가 굳이 가지고 있을 이유는 없고 편리성을 위해 넣어도 신경쓸 게 많아져서 더 피곤해진다. 이는 OrderItem과 Item을 보면 좀 더 감이 오는데

Item은 통계인 경우가 아니면 자기 자신을 누가 자신을 구매했는지 같은건 별 필요 없는 정보다. 이렇게 필요 없어서 없애야 하는 정보를 굳이 가지고 있어서 더 복잡하게 만드는 것. 이 객체가 어디까지 가지고 있어야 하는지, 잘 끊어내는 것도 설계 방법이다.

 

 

하지만 예제니까 해본다.

 

 

Order와 OrderItem은 연관이 많아서 양방향으로 할만하다.

 

편리성이나 비즈니스 이유 등으로 양방향으로 만들 수 있겠지만 여기선 굳이 member가 orders를 가질 필요는 없다.. 그냥 판단하면 된다.