본문 바로가기

JPA 프로그래밍

[자바 ORM 표준 JPA 프로그래밍] 연관관계 매핑 기초 - 양방향 연관관계 와 연관관계의 주인( 2/3 )

※ 이 강의에서 아래의 두가지가 제일 중요하다고 합니다.

  • 영속성 컨텍스트 메커니즘
  • 양방향 연관관계와 연관관계의 주인

 

메뉴얼에 있는 기능만 쓰지 말고 잘 알고 넘어가야 합니다.

 

테이블은 단방향과 양방향은 차이가 없습니다. 테이블의 경우에는 TEAM_ID를 알고 있으면 MEMBER, TEAM의 정보를 다 알 수 있다. 그러나 이전의 단방향의 경우에는 TEAM에서 MEMBER에 대한 정보(ex. username)를 알 수가 없습니다.

 

..
	@OneToMany(mappedBy = "team")// 1: 다 매핑에서 뭐랑 매핑 되는건가?, 반대편 사이트
    private List<MemberRelation> members = new ArrayList<MemberRelation>();
 ...

https://github.com/oss0202/ex1-hello-jpa/blob/master/src/main/java/relationmappingbasic/JpaMainrRelationMany.java

 

oss0202/ex1-hello-jpa

JPA Hello World. Contribute to oss0202/ex1-hello-jpa development by creating an account on GitHub.

github.com

 

- 연관관계의 주인과 mappedBy

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

 

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

  • 객체 연관관계 = 2
    • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
    • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다. 
      • 회원 -> 팀 연관관계 1개( 단방향 )
      • 팀 -> 회원 연관관계 1개( 단방향 )
  • 테이블 경관관계 = 1
    • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
    • 회원 <-> 팀의 연관관계 1개( 양방향 )

 

- 의문? TEAM_ID를 수정하고 싶다면 Member와 Team 어디를 수정해야 하는가?

TEAM_ID를 수정하려고 할 때, Meber객체에 있는 team(Team), Team객체에 있는 members(List) 이 둘 중 어떤 것을 수정해야 하는가? 단방향일 경우에는 상관이 없지만, 양방향이 되면서 이 문제가 생깁니다.

 

-> 해결방법 : 둘(Member, Team) 중 하나로 외래키를 관리해야 한다.

- 연관관계의 주인( Owner )

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

 

  • 누구를 기준으로 해야하는가?
    • 외래 키가 있는 곳을 주인으로 정해라
      • 외래키가 있는곳은 무조건 다
      • 외래키가 없는 곳은 일
    • 여기서는 Member.team이 연관관계의 주인

 

- 양방향 매핑시 가장 많이 하는 실수

1. 연관관계의 주인에 값을 입력하지 않음

Team team = new Team();
team.setName("TeamA");
em.persist(team);

MemberRelation member = new MemberRelation();
member.setUserName("member1");

// 역방향( 주인이 아닝 방향 )만 연관관계 설정
team.getMembers().add(member);

em.persist(member);

연관관계의 주인은 MemberRelation에 있는 team(Team)이다.

Team에 있는 members(List<MemberRelation>)은 읽기 전용이다.

 

2. 올바른 연관관계 매핑사용

Team team = new Team();
team.setName("TeamA");
em.persist(team);

MemberRelation member = new MemberRelation();
member.setUserName("member1");
// 올바른 연관관계 설정
member.setTeam(team);
em.persist(member);

3. Team에 있는 Mebmers조회

- flust, clear 진행( 쿼리 실행 및 영속성 컨텍스트 초기화 )

2. 올바른 연관관계 매핑사용 이후 작성
..
em.flush();
em.clear();

Team findTeam = em.find(Team.class, team.getId());
System.out.println("------------------------------------");
List<MemberRelation> members = findTeam.getMembers();

for (MemberRelation memberRelation : members) {
    System.out.println("m = " + memberRelation.getUserName());
}
..

'2.올바른 연관관계 매핑사용' 에서 List에 세팅한 값이 없어도 올바르게 가져온다. 왜냐하면 'em.find' 실행 시 DB에서 Select 쿼리를 실행시키기 때문이다. 그래서 'team.getMembers().add(mamber)'를 통해서 team의 members값에 추가하지 않아도 올바르게 조회가 된다.

 

 

- 1차 캐시( 영속성 컨테스트 )에서 조회

            ...
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            MemberRelation member = new MemberRelation();
            member.setUserName("member1");
            member.setTeam(team);
            em.persist(member);

            // 1차 캐시( 영속성 컨텍스트 )에 추가
            team.getMembers().add(member);

//            em.flush();
//            em.clear();

            Team findTeam = em.find(Team.class, team.getId()); // 1차 캐시
            System.out.println("------------------ 1 ------------------");
            List<MemberRelation> members = findTeam.getMembers();

            System.out.println("------------------ 2 ------------------");
            for (MemberRelation memberRelation : members) {
                System.out.println("m = " + memberRelation.getUserName());
            }

            System.out.println("------------------ 3 ------------------");
            tx.commit();
            ...

위처럼 영속성 컨텍스트에 있는 team에 member를 넣어줘야 올바르게 조회를 한다. ( 쿼리 실행 X, 1차 캐시에 있는 데이터 조회)

3. 결론

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
    • 위 코드처럼 member, team 양쪽에 설정
  • 연관관계 편의 메소드를 생성하자
    • 작업을 진행하다가 까먹을 수도 있으므로 별도 메서드 생성
    • ex. chacneTeam에 코드 추가, setTeam메서드를 사용해도 되지만 보통 단순 값 작업(get, set)을 할때만 주로 사용합니다. 그래서 추가 로직이 들어가는 경우 별도로 메서드를 생성해서 진행하
    • 물론 반대로도 설정 가능합니다. 
    • 하지만 동일한 기능을 양쪽에 생성해 놓을 경우 문제가 될 수 있다.( 무한루프, 유지보수의 힘듬, 로직 수정 시 한쪽만 수정할 수 있음, ...)
    • 그래서 둘 중에 하나를 정해서 사용하면 된다.
  • 양방향 매핑시에 무한 루프를 조심하자
    • ex. toString(), lombok, JSON 생성 라이브러리
      • 실제로 운영에서 자주 발생하는 문제
        • Entity를 직접 Controller에서 response로 Entity를 직접 보낼 때, 양방향으로 연관관계가 설정이 되어 있다면 Entity를 JSON으로 변혼할떄 무한루프가 걸린다.
        • 해답
          • Lombok에서 지원하는 toString 사용하지 말 것, 사용하더라도 아래처럼 무한루프가 발생할 하는 부분은 제거할 것
          • Controller에서 Entity자체를 리턴하지 않도록 해야한다.
            • 그렇게 안할 경우
              • 무한루프가 발생할 수 있다.
              • Entity에 변경사항이 발생할 경우 API 스팩이 바뀐다. 그래서 문제가 커질 수 있다.
          • 그래서 Entity는 Dto로 변환(값만 있는)하는 것을 추천한다.
            • response, request 모두 

 

- 마무리 정리

- 양방향 매핑 정리

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

 

- 연관관계의 주인을 정하는 기준

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함

 

 

 

 

 

출처

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

www.inflearn.com