JPA

[JPA 8] 연관관계 - 방향성

snowkit 2022. 3. 10. 20:53

연관관계의 종류

  • Many-To-One
  • One-To-Many
  • One-To-One
  • Many-To-Many

DB 테이블과 객체의 차이

  • DB 테이블
    • 외래 키 컬럼을 생성하고 외래 키와 조인을 사용해서 연관된 테이블을 조회
  • 객체
    • 연관된 객체를 선언하고 참조를 통해 객체를 조회

객체의 방향성

설명

  • DB에선 방향이라는 개념이 존재하지 않는다
    • SELECT * FROM member JOIN team ON member.team_id = team.id;
    • SELECT * FROM team JOIN member ON member.team_id = team.id;
    • 두 SQL은 동일한 결과를 표시
  • 객체는 방향이라는 개념이 존재한다(단방향 참조, 양방향 참조)
    • 단방향 참조
      • Member에 getTeam() 메소드가 존재하지만 Team에는 getMembers() 메소드가 존재하지 않는 경우
      • Team에 getMembers() 메소드가 존재하지만 Member에는 getTeam() 메소드가 존재하지 않는 경우
    • 양방향 참조
      • Member에 getTeam() 메소드가 존재하고 Team에도 getMembers() 메소드가 존재하는 경우
      • 서로 다른 단방향이 2개 있는 것(반대 방향으로도 조회할 수 있는 기능이 추가된 것)
  • 단방향 매핑만 해도 연관관계 매핑은 완료된다

단방향(Unidirectional)

  • 연관된 두 Entity 중 하나의 Entity에서만 상대편을 참조(컴포지션)하고 있는 상태
    • 예시
      • Many 객체(Member)에서만 One 객체(Team) 조회 가능
      • One 객체(Team)에서만 Many 객체(Member) 조회 가능

양방향(Bidirectional)

  • 연관된 두 Entity에서 서로 상대편을 참조(컴포지션)하고 있는 상태
    • 예시
      • Many 객체(Member)에서도 One 객체(Team)를 조회할 수 있고, One 객체(Team)에서도 Many 객체(Member) 조회 가능

1. 양방향 연관관계의 참조측(referencing side), 소유측(owning side)

  • 양방향 매핑일 때 (mappedBy = "소유측 Entity에 선언된 참조측 Entity의 변수명")으로 참조측과 소유측 설정 가능
    • 예시: @OneToMany(mappedBy = "team")
    • mappedBy를 선언하더라도 DB에 영향을 미치지 않음

참조측(referencing side)

  • mappedBy가 선언된 Entity
  • 양방향 연관관계의 참조측은 Read만 가능
    • getMembers().get(0)을 수정 후 teamRepository.save(team) 하더라도 DB에 반영되지 않음

소유측(owning side)

  • 양방향 연관관계의 소유측은 CRUD 전부 가능
    • getMembers().get(0)을 수정 후 memberRepository.save(member) 하면 DB에 반영됨
  • JPA에서 외래 키를 관리(등록, 수정)하는 Entity
    • 일반적으로 외래 키를 가지고 있는 Many Entity를 연관관계의 주인으로 설정한다

2. 양방향 매핑에서 주의할 점

1. 1차 캐시

Team team = new Team();
team.setName("한국");

Member member = new Member();
member.setName("A");
member.setTeam(team);

// 영속성 컨텍스트에 저장
entityManager.persist(member);
entityManager.persist(team);

// 쿼리하면 DB가 아닌 영속성 컨텍스트의 1차 캐시에서 가져온다
Team foundTeam = entityManager.find(Team.class, team.getId());
List<Member> members = foundTeam.getMembers();

// Member A가 있는데도 불구하고 1이 아닌 0이 출력된다
System.out.println(members.size());
  • Member와 Team을 영속성 컨텍스트에 저장한 뒤(트랜잭션 커밋되지 않은 상태) find() 메소드로 조회하면 1차 캐시에서 조회하게 된다
  • member.setTeam() 메소드를 사용했기 때문에 member.team을 가져올 수 있지만, team.members에는 아무 것도 들어있지 않다
  • team.members에 값을 저장하는 방법
    • member.setTeam(team)과 함께 team.getMembers().add(member)를 실행한다
      • Member의 setTeam(Team team) 메소드 안에 team.getMembers().add(this) 코드를 추가하면 편하게 사용 가능
      • 이 방법을 사용한다면 기본 setter 메소드는 네이밍 때문에 헷갈릴 수도 있으므로 setTeamBidirectional()같은 새로운 메소드를 생성해서 사용하는 것을 권장한다 (하단의 예시1 참고)
    • Member의 setTeam(team)을 사용하지 않고 Team에 addMembersBidirectional() 메소드를 생성 후 member.setTeam(this)this.members.add(member) 코드를 추가해서 사용해도 된다 (하단의 예시2 참고)
      • 위의 메소드는 Member와 Team에서 중복으로 사용하면 버그가 발생할 수 있으므로 Member에 생성할지 Team에 생성할지 하나만 선택해서 사용한다
    • entityManager.clear()로 영속성 컨텍스트를 비우면 find() 메소드는 1차 캐시가 아닌 DB에서 가져오기 때문에 team.members에 값이 들어있는 것을 확인할 수 있다
      • 영속성 컨텍스트의 내용을 비우기 전 entityManager.flush()entityManager.commit() 메소드로 먼저 DB에 반영 필수
  • 예시1
@Entity
public class Member {
    /* ... */
    public void setTeamBidirectional(Team team) {
        this.team = team;
        team.getMembers.add(this);
    }
}
  • 예시2
@Entity
public class Team {
    /* ... */
    public void addMembers(Member member) {
        member.setTeam(this);
        this.members.add(member);
    }
}

2. 순환 참조

  • 양방향 매핑에서 toString() 재정의
    • Many 객체(Member)와 One 객체(Team)에 둘 다 toString()을 재정의하면 순환참조에 의해 StackOverFlow가 발생할 수 있다
    • 최소한 한 쪽은 toString()에서 반대편 객체를 매핑하지 않도록 주의: @ToString(exclude = "members")
  • 양방향 매핑에서 JSON 생성 라이브러리
    • 객체를 JSON으로 변환하는 경우 다음과 같은 오류가 발생한다
      1. Team 안에 members 객체가 있으므로 연관된 모든 Member 객체를 찾는다
      2. Member의 Team을 확인하는 순간 1번으로 돌아가서 다시 그 Team의 모든 Member 객체를 찾는다
      3. 무한 반복
    • Spring은 REST Controller에서 jackson 라이브러리가 객체를 자동으로 JSON으로 변환하기 때문에 Controller에서 Entity를 그대로 응답하지 않도록 한다(DTO 사용)

3. 양방향 매핑 사용 여부

  • 양방향 매핑은 코드상으로 확인하기 어려운 신경써야 할 부분이 많기 때문에 웬만하면 양방향 매핑은 사용하지 않는 것을 권장하며, 꼭 필요한 경우만 사용한다

'JPA' 카테고리의 다른 글

[JPA 10] 연관관계 - 연관관계 매핑 편의성  (0) 2022.03.10
[JPA 9] 연관관계 - 저장, 변경  (0) 2022.03.10
[JPA 7] 연관관계 - @OneToMany  (0) 2022.03.10
[JPA 6] 연관관계 - @ManyToOne  (0) 2022.03.10
[JPA 5] 영속성 컨텍스트  (0) 2022.02.22