본문 바로가기

IT공부/JPA

JPA 5강 - 양방향 매핑

JPA 5강 - 양방향 매핑

연관관계의 주인과 mappedBy

  • mappedBy = JPA의 멘붕 클래스1
  • mappedBy는 처음에는 이해하기 어렵다.
  • 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다. 

객체와 테이블 간에 연관관계를 맺는 차이를 이해하면 mappedBy를 이해할 수 있다. 현재 1. Member 클래스와 2. Team 클래스로 방향관계가 2가지가 있다. 

객체와 테이블이 관계를 맺는 차이

  • 객체 연관관계

      회원 -> 팀 연관관계 1개(단방향)

      팀 -> 회원 연관관계 1개(단방향)

  • 테이블 연관관계

      회원 <-> 팀의 연관관계 1개 (양방향)

객체의 양방향 관계

  • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다. 
  • A -> B (a.getB())         

  • B -> A (b.getA())

테이블의 양방향 연관관계

  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽으로 조인할 수 있다.)

SELECT *

 FROM MEMBER M

   JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

 

SELECT *

 FROM TEAM T

   JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID

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

member.setTeam(team)을 하면 member 객체의 team이 바꼈다. 그렇다면 team에 members에 member객체를 넣으면 어떻게 될까? MEMBER 테이블에 들어가는게 맞다. member 객체도 MEMBER 테이블을 관리하고 team 객체도 MEMBER 테이블 관리하는 것이다. 객체의 참조라는 것과 DB의 외래키라는 것은 전혀 다른 개념이다. 객체는 2개인것이고 테이블은 하나이다. member.setTeam(team)을 해도 MEMBER 테이블이 바껴야되고 team.setMembers(members)를 해도 MEMBER 테이블이 바껴야 된다. 그렇다며 둘 중에 뭘 믿어야 할까? 그래서 둘 중에 하나를 주인으로 하고 나머지는 조회만 하게하자고 정한 것이다. 

연관관계의 주인(Owner)

양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아니쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용X
  • 주인이 아니면 mappedBy 속성으로 주인 지정

Member 클래스와 Team 클래스 중 하나만 주인을 정할 수 있다. 예를 들어 Member 클래스를 주인으로 정하면 member.setTeam(team)으로 업데이트나 등록을 할 수 있다. 반대로 Team 클래스를 주인으로 하면 team.getMembers.add(member)으로 업데이트나 등록을 할 수 있다. 둘 다 동시에 집어넣으면 연관관계 주인의 것만 영향을 준다. 주인은 어떻게 지정할까? 결론을 말하자면 mappedBy를 썻다는 것은 해당 클래스가 mappedBy에 의해서 매핑이 됬다는 의미이다. JPA 4강에서 Team 클래스에 mappedBy를 지정했다. 아래 코드를 보자.

 

 

  1. @Entity
  2. public class Team {
  3.    
  4.    @Id @GeneratedValue
  5.    private Long id;
  6.  
  7.    private String name;
  8.  
  9.    @OneToMany(mappedBy = "team")
  10.    List<Member> members = new ArrayList<Member>();
  11.    ...
  12. }

라인 9 @OneToMany(mappedBy = "team")코드를 보면 @OneToMany 어노테이션의 속성으로 mappedBy를 지정해주었다. mappedBy 속성값으로는 team을 지정해주었는데 이는 Member 클래스에 team 필드에 의해서 매핑이 되었다고 명시하는 것이다. Member 클래스와는 달리 @JoinColumn 어노테이션도 없다. 아래는 Member 클래스의 코드이다.

 

  1. @ManyToOne
  2. @JoinColumn(name = "TEAM_ID")
  3. private Team team;

라인 2처럼 Member 클래스가 어디와 조인되었는지 명시해주지만 Team 클래스에는 @JoinColumn 어노테이션이 없다. 이 의미는 Team 클래스는 Member 클래스에 의해서 매핑이 되었을 뿐이다라는 의미이다. 

누구를 주인으로?

  • 외래 키가 있는 곳을 주인으로 정해라
  • 여기서는 Member.team이 연관관계의 주인 

지금 외래키가 MEMBER 테이블에 TEAM_ID에 설정되어 있다. 그러면 Member 클래스를 주인으로 정하자. 개발자들이 인지를 해야 한다. Team 클래스를 주인으로 정하면 값을 변경했는데 다른 테이블에 있는 값의 업데이트 문이 날라간다. JPA에 대해서 정말 깊이 있게 이해하지 않는 이상 혼란스러워진다. team의 값을 변경했는데 왜 MEMBER 테이블의 값이 변경되는지 혼란스러워지는 것이다. 지금은 테이블이 하나지만 현업에서는 테이블이 수십개다. 사실 객체지향설계를 공부해보면 순환참조(양방향 연관관계)는 좋은 것은 아니다. 사실 단방향 연관관계가 좋다. 그래서 Member 클래스에 team 필드만 정의하고 주인만 정의하고 단방향 연관관계로 끝내버리는 것이 좋다. 설계 시 단방향 연관관계로 설계를 다 끝내고 개발을 한다. 개발 도중에 Team 클래스에서도 Member 클래스를 조회를 해야된다는 필요성이 생기면 그 때 양방향으로 개발을 하는 것이다. 단지, DB에도 영향을 주지 않고 자바코드만 추가하면 된다. 양방향 연관관계는 단순히 조회하는 것을 편하게 하려고 부가기능이 더 들어간거라고 보면 된다. 그리고 Team이 주인일 때 등록을 한다고 하면, members필드때문에 update 쿼리가 한번더 일어난다. 즉, team을 등록하면서 member를 추가하는 것이다. DB에 등록이 되야하니 INSERT문이 실행되고 그 후 members 필드에 대해서 그 다음에 UPDATE 쿼리가 나간다. 하지만 MEMBER가 주인일 때 등록하면, MEMBER 테이블 INSERT 시 한 번에 team 필드도 같이 INSERT한다. 

양방향 매핑시 가장 많이 하는 실수(연관관계의 주인에 값을 입력하지 않음)

Team team = new Team();

team.setName("TeamA");

em.persist(team);

 

Member member = new Member();

member.setName("member1");

 

//역방향(주인이 아닌 방향)만 연관관계 설정

team.getMembers().add(member);

 

em.persist(member);

ID

USERNAME

TEAM_ID

1

member1

null

 

위 코드를 보면 member.setTeam(team)으로 member 객체에 team 객체를 세팅하지 않았다. 반대의 경우인 team.getMembers().add(member)로 team 객체에만 member를 세팅했다. 그러면 결과는 아래 표와 같이 MEMBER 테이블 TEAM_ID 컬럼에 null로 들어간다. 

 

아래와 같이 Main 클래스를 수정하고 실행해보자.

  1. try {
  2.        Team team = new Team();
  3.        team.setName("teamA");
  4.        em.persist(team);
  5.                      
  6.        Member member = new Member();
  7.        member.setName("hello");
  8.        em.persist(member);
  9.        team.getMembers().add(member);
  10.                      
  11.        em.flush();//DB 쿼리를 보낸다.
  12.        em.clear();//캐시를 비운다.
  13.  
  14.  
  15.        tx.commit();
  16. } catch (Exception e) {

아래와 같이 DB를 조회해보면 MEMBER 테이블 TEAM_ID 컬럼에 null이 들어간 것을 확인할 수 있다. 이번에는 라인 9를 주석처리하고 member.setTeam(team); 코드를 넣어 실행해보자.

라인 9를 주석처리하고 member.setTeam(team); 코드를 넣어 실행해보니 아래와 같이 MEMBER 테이블에 1이 들어갔다.

주인이 아닌 것은 무시되는 것이다. 즉 Member 테이블이 주인이기 때문에 주인에서 등록한 것만 제대로 등록이 되는 것이다. 

양방향 매핑시 연관관계의 주인에 값을 입력해야 한다.(순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력해야 한다.)

현업에서는 어떻게 해야할까? 그냥 2군데 다 값을 넣자. 2군데 다 조회가 되게 하고 싶다면. 객체 입장에서 보면 2군데다 넣어주는 게 맞다. DB에서는 주인관계에 의해 주인만 등록, 업데이트가 가능하더라고 객체 입장에서는 2군데다 조회가 되어야한다. 그래서 실제 코드를 작성할 때는 아래와 같이 작성해야 한다.

 

Team team = new Team();

team.setTeam("TeamA");

em.persist(team);

 

Member member = new Member();

member.setName("member1");

 

team.getMembers().add(member);

//연관관계의 주인에 값 설정

member.setTeam(team);

 

em.persist(member);

양방향 매핑의 장점

  • 단방향 매핑으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색)기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨.(테이블에 영향을 주지 않음)

연관관계 매핑 어노테이션

  • 다대일(@ManyToOne)
  • 일대다(@OneToMany)
  • 일대일(@OneToOne)
  • 다대다(@ManyToMany)
  • @JoinColumn, @JoinTable

상속 관계 매핑 어노테이션

  • @Inheritance
  • @DiscriminatorColumn
  • @DiscrimnatorValue
  • @MappedSuperclass(매핑 속성만 상속)

객체를 상속관계로 하는 모델링을 할 수 있다. (JPA 1강 Item 클래스와 Album 클래스 상속관계처럼) DB에서 ITEM 테이블과 ALBUM 테이블로 나눌지 아니면 한테이블로 만들지를 결정할 수 있다. 객체는 객체지향적으로 설계가 되있는데 DB는 한테이블로 설계할 수 있다. 

복합키 어노테이션

  • @IdClass
  • @EmbeddedId
  • @Embeddable
  • @MapsId

지금까지는 PK가 하나였다. 예를 들어 이름과 전화번호 2개를 묶어서 PK로 설정한다면 위 어노테이션을 사용하여 매핑을 따로 해야 한다. JPA에서는 복잡하지만 할 수는 있다.

'IT공부 > JPA' 카테고리의 다른 글

JPA 7강 - JPA 객체지향쿼리  (0) 2020.05.22
JPA 6강 - JPA 내부구조  (0) 2020.05.22
JPA 4강 - 연관관계 매핑  (0) 2020.05.21
JPA 3강 - 필드와 컬럼 매핑  (0) 2020.05.21
JPA 2강 - JPA기초와 매핑  (0) 2020.05.20