당니의 개발자 스토리

다대일 [N:1] 본문

스프링/자바 ORM 표준 JPA 프로그래밍 - 기본편

다대일 [N:1]

clainy 2024. 6. 21. 17:21

다대일 [N:1]

이번 시간에는 다양한 연관관계 맵핑에 대해서 알아보겠습니다.

우선 이번 시간에는 연관관계 맵핑에 나오는 것을 대부분 다 알아보는 시간입니다. 딱 정리를 해드릴 거고요. 기본적인 고려 사항이랑 다대일, 일대다, 일대일, 다대다. 사실 연관관계라는 것은 이렇게 다중성으로 따지고 보면 사실 이 네 가지가 다거든요. 이 네 가지 케이스를 다 정리를 해서 말씀드리겠습니다.

그리고 이 네 가지 케이스들을 다 포함하는 연관관계 맵핑을 실전 예제 3번으로 해보겠습니다.

우선 연관관계 맵핑 시에 고려해야 될 사항이 세 가지가 있는데요.

다중성, 다중성이라는 것은 일대다 뭐 이런 거겠죠. 그리고 단방향이냐, 양방향이냐, 그리고 양방향일 때의 연관관계의 주인, 이렇게 세 가지를 꼭 고려하셔야 되구요.

다중성은 아까 말씀드렸듯이 다대일, 일대다, 일대일, 다대다, JPA가 이 4가지 애노테이션을 다 제공해줍니다. 이걸 가지고 쓰시면 됩니다. 사실 이 다중성은 약간 헷갈리실 수 있는데, JPA에서 나오는 Annotation은 전부 다 DB랑 맵핑하기 위해서 있다고 보시면 돼요. 그래서 데이터베이스 관점에서의 다중성을 기준으로 고민을 하시면 됩니다.

그리고 약간 한 번씩 좀 헷갈릴 때가 있어요. 애매할 때는 반대쪽으로 생각해보시면 돼요. 예를 들어 회원과 Team이면 반대인 Team과 회원의 관계를 생각해보시면 돼요. 왜냐하면 이게 대칭성이 있거든요. 일대일의 반대는 일대일이에요. 일대다의 반대는 다대일이에요. 다대다의 반대도 당연히 다대다예요. 그래서 대칭성이 있다고 보시면 됩니다.

여기서 사실 중요한 게 있어요. 제가 이 강의의 장점이라고 생각하는 건데 사실 여러분 다대다는 실무에서 쓰면 안 되는거에요. 제가 이런 거를 잘 걸러서 알려드릴게요. 거의 정말 많이 쓰는건 이 다대일을 가장 많이 쓰구요 이제 일대다도 좀 필요할 때 많이 씁니다. 그리고 일대일도 가끔 나오구요. 근데 다대다는 사실 실무에서 쓰면 안되는 겁니다. 왜 쓰면 안되는지, 이제 어떤 방식으로 대체해야 되는지 뒤에서 한번 설명드리겠습니다.

자 그 다음에 이제 단방향, 양방향. 제가 이전 시간에 연관관계 맵핑 기초에서 설명드렸는데 이번 시간에 정리를 해드릴게요.

테이블은 외래키 하나로 양쪽으로 조인을 할 수 있습니다. 그렇기 때문에 사실 방향이라는 개념이 없어요. 외래키를 양쪽에 두는 게 아니잖아요. Member랑 Team이 있으면 이걸 양쪽에 두는 게 아니라 그냥 한쪽에만 외래키를 딱 세팅을 하면 Member에서 Team을 조인할 수도 있고, Team에서 나와 관련된 Member 쪽으로 반대로 조인할 수도 있단 말이에요. 외래키 하나로 양쪽 조인이 다 가능하기 때문에 사실 테이블은 방향이라는 개념이 없어요.

그런데 이제 객체는 참조용 필드가 있는 쪽으로만 참조를 할 수가 있거든요. 그렇기 때문에 예를 들어서 Member에서 Team으로 가고 싶으면 Member 안에 Team으로 가는 레퍼런스가 있어야 되고, Team 입장에서 Member를 보고 싶으면 또 반대로 Team 안에 Member로 가는 레퍼런스 참조가 있어야 된단 말이에요. 참조용 필드가 다 있어야 갈 수 있어요. 그래서 객체는 한쪽만 참조하면 단방향이라고 그러고요. 양쪽이 서로 참조하면 양방향이라고 하는데, 사실 이 양방향이라는 것도 사실 양방향이라는 게 없어요. 이거는 그냥 사람들 이해하기 쉽게 용어를 만든 거지 객체 입장에서는 참조라는 것만 보면 방향이 하나에요. 그러니까 Member에서 Team에 대한 참조가 있죠. 반대로 Team에도 Member에 대한 참조가 있단 말이에요. 그래서 참조 입장에서 보면 그냥 단방향이 두 개가 있는 거예요. 마치 이게 양쪽으로 거니까 양방향인 것처럼 보이는 거지 사실은 단방향이 두 개가 있는 겁니다. 제가 이렇게 설명을 드린 이유는

그래야 연관관계의 주인이라는 개념을 명확하게 이해할 수 있거든요. 테이블은 외래키 하나로 두 테이블이 연관관계를 맺는단 말이에요. 근데 문제는 객체에서의 양방향 관계는 A가 B도 참조해야 되고, B도 A를 참조해야 되는 것처럼 참조해야 되는 포인트가 두 군데에요. 근데 테이블은 하나란 말이에요. 양방향인 경우에 외래키는 하나고 객체의 참조는 두 개란 말이에요. 그러면 결국 객체 양방향 관계는 참조가 두 군데 있고 결국 이 둘 중에 하나를 택해서 테이블의 외래키를 관리할 곳을 정해줘야 됩니다.

예를들어 A->B, B->A 예를 들어서, Member랑 Team이면 Member에 있는 Team 레퍼런스를 바꿨을 때 뭔가를 해줄지, 반대로 Team에 있는 Member 레퍼런스를 바꿨을 때 뭔가를 해줄지, 이 둘 중에 뭘 손댔을 때 바꿀 건지를 딱 하나를 정해야 돼요. 그래서 연관관계 주인이라는 개념이 나오고, 둘 중에 외래키를 관리하는 참조가 연관관계 주인이 됩니다. 그리고 연관관계 주인의 반대편은 외래키의 값을 업데이트하는 데 영향을 주지 않습니다. 단순하게 데이터를 조회하는 기능만 가능합니다. 혹시 이 내용이 잘 이해가 안되시면 이전에 연관관계 설명하는 기초편을 꼭 봐주시기 바랍니다.

그러면 JPA에서 가장 많이 사용하고, 여러분이 꼭 아셔야 되는 다대일부터 할게요. 사실 이거는 앞에서 연관관계 맵핑을 처음 설명 드릴 때 설명을 쭉 드린 건데 이번에 정리한다고 보시면 될 것 같아요.

가장 많이 사용하는 다대일 단방향은 이렇게 됩니다.

Member랑 Team이 있으면, DB 입장에서 생각해보면 당연히 Team이 1이고 Member가 N이란 말이에요. 그러면 다 쪽에 외래키가 가야 됩니다. 이거는 DB 설계상, DB 테이블을 설계할 때 일대다 라고 하면 항상 다에 외래키가 가야 돼요. 안그러면 이제 설계가 잘못된 거죠. 다 쪽에 외래키가 있어야 되는 게 생각을 해보시면 Member를 두 명 넣어도 TEAM_ID에다가 같은 Team을 넣어줄 수 있잖아요. 반대로 TEAM에 MEMBER_ID를 넣게 외래키로 되면 Member를 두 개 넣으면 그때마다 Team을 여러 개 Insert해야 하니까 설계가 딱 잘못 나와버려요. 아무튼 관계형 DB에서는 다 쪽에 항상 외래키가 들어가줘야 설계가 맞다라고 보시면 될 것 같고요.

그래서 객체에서 참조를 '난 Member에서 Team으로만 참조할 거야' 라는 이런 굉장히 단순한 시나리오인 경우만 생각해보면 그냥 이 TEAM_ID를 그냥 끌어와 가지고 Member의 team을 세팅해 놓고 이렇게 맵핑을 딱 걸면 됩니다. 그래서 외래키 있는 걸 기준으로 그냥 거기에 연관된 참조를 그냥 넣어서 맵핑을 딱 걸면 됩니다.

이 MEMBER 입장에서 이 TEAM_ID도 TEAM으로 찾아가기 위한 거고, 같은 레벨로 Member 입장에서 이 team이라는 참조값도 똑같은 내용입니다. 이 Team을 찾아가고 싶어서 참조를 만든 거죠. 그럼 team과 TEAM_ID, 둘이 그냥 맵핑을 딱 걸면 되는 거예요. 외래키가 있는 곳(MEMBER)에 그냥 참조(team)을 걸고 연관관계 맵핑을 딱 하면 된다라고 하는 게 다대일 단방향 맵핑입니다.

이게 가장 많이 사용하는 연관관계구요. 다대일의 반대는 일대다가 되겠죠.

자 그럼 이제 이걸 양방향으로 만들면 어떻게 하면 되냐, 우선 양방향을 하기 전에 제가 코드로 단방향을 다시 한번 보여드릴게요.

Member에서 이거는 양방향일 때니까

Team으로 가서 이걸 잠깐 지우고요.

이것도 지워줍니다. Member로 가면,

아까 봤던 다대일 단방향 그림이 이거죠. 이제 Member에서 Team으로 가고 싶으면, 이렇게 그냥 가는 거죠.

/

@ManyToOne으로 잡고 JoinColumn 해서 'TEAM_ID 외래키랑 team을 맵핑 할 거야' 라고 잡아 주시면 돼요.

그러면 이제 Team에는 Member가 없겠죠. Team은 Member로 가고 싶은 의지가 없는 거예요. 참조하고 싶은 의지 자체가 없기 때문에 그냥 두면 돼요. 이게 바로 다대일 단방향 맵핑 입니다.

그러면 여기에 추가로 양방향은 어떻게 되냐면, Member의 반대쪽 사이드를 하나 넣어 주시면 돼요. Team에다가 넣는 거겠죠. 근데 여기도 중요한 게 반대쪽 사이드를 추가한다고 해서 테이블에 영향을 전혀 주지 않습니다. 어차피 Member의 team이 연관관계의 주인이기 때문에 TEAM_ID를 관리하고 있단 말이에요. 그래서 반대쪽은 어차피 읽기만 가능하기 때문에 Team의 members를 그냥 객체에서 추가만 해주시면 됩니다. 제가 참고로 여기 지금 일대다, 일대일, 다대일 이 ppt 제목에서 가장 앞에 나오는 단어가 연관관계의 주인이라고 보시면 됩니다.

이 케이스는 다대일 양방향이면 이 '다'가 연관관계 주인이라고 그렇게 기준을 잡고 여러분들께 정리를 해드리는 거에요.

이제 양방향으로 가고 싶다, 그러니까 Member에서 Team을 잡아서 단방향이 됐는데 하다보니까 Team에서 Member들 소속인 걸 알아야 되는 로직이 너무 자주 등장하는 거예요.

그러면 반대를 추가해 주시면 되죠.

그리고 @OneToMany 하고 mappedBy 해서 team을 넣어주시면 됩니다. 이 team은

Member에 있는 이 team 변수명 입니다.

이렇게 잡아주시면 '아 나는 이 team 이라는 걸 위해서 맵핑이 되어진 애야' 라는 의미로 단순하게 읽기만 가능합니다.

이렇게 해서 다대일 양방향을 봤구요. 외래키가 있는 쪽이 연관관계의 주인이다. 그 다음에 양쪽을 서로 참조하도록 개발해라. 양쪽을 서로 참조하도록 개발할 때 이제 필요한 겁니다.

자 그러면 이제부터는 새로운 내용이 등장합니다. 다음 시간에 하겠습니다.