티스토리 뷰

ORM/JPA

[JPA] 3. 연관관계 매핑

mandykr 2021. 11. 9. 11:33

1. 단방향 연관관계

테이블은 외래키를, 객체는 참조를 이용해 연관관계를 설정한다.

Member 클래스가 TeamId 가 아닌 Team 객체를 참조함으로써 객체지향적 설계가 가능하다.

 > 연관관계 매핑

 @Entity
 public class Member {
   @Id @GeneratedValue
   @Column(name = "MEMBER_ID")
   private Long id;
   
   @Column(name = "USERNAME")
   private String name;
   private int age;
   
   // Member - Team : 다대일 관계
   @ManyToOne
   @JoinColumn(name = "TEAM_ID")
   private Team team;
   
   // getter, setter
}

@Entity
public class Team {
  @Id @GeneratedValue
  @Column(name = "TEAM_ID")
  private Long id;  
  private String name;
  
  // getter, setter
}

 > 객체 참조를 통한 저장 및 조회

//1.저장
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);

//2.조회
//조회 - 1차 캐시에서 member와 team조회
Member findMember = em.find(Member.class, member.getId());

//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();

//3.수정
// 새로운 팀B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);

// 회원1에 새로운 팀B 설정 - 
// 영속성 컨텍스트에 저장한 후 tx 커밋시점에 update 쿼리 실행
member.setTeam(teamB);

 

2. 양방향 연관관계

테이블 사이에서는 TEAM_ID를 통해 TEAM에 속한 MEMBER를 조회할 수 있지만

객체 사이에서는 Team을 통해 속한 Member를 찾을 수 있는 방법이 없다.

 

Team 클래스의 인스턴스 변수로 Member List를 갖도록 설계해서 양방향 연관관계를 설정한다.

 

@Entity
public class Team {
  @Id @GeneratedValue
  private Long id;
  private String name;
  
  // Team - Member : 일대다 관계
  // ArrayList로 초기화 : nullpointerexception 방지
  @OneToMany(mappedBy = "team")
  List<Member> members = new ArrayList<Member>();
  
  // getter, setter
}

> mappedBy : 클래스 자신이 연관관계의 반대편에 어떤 이름의 참조변수로 설정되어 있는지를 명시

외래키가 있는곳을 주인으로 정하자.

Member 클래스의 team과 Team 클래스의 members 모두 값을 변경할 수 있는데

MEMBER 테이블이 갖고있는 TEAM_ID 외래키를 update 하기 위해선 값의 명확한 주인이 필요하다.

연관관계 주인만 값(외래키)를 관리할 수 있다.

주인이 아닌 경우 mappedBy 속성으로 주인을 지정해 주어야 한다.

@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 주인인 Member.team은 MEMBER 테이블의 TEAM_ID 외래키를 업데이트 한다.
Member member = new Member();
member.setTeam(team);
em.persist(member);

// 주인이 아닌 Team.members 는 MEMBER 테이블의 TEAM_ID 외래키를 업데이트 하지 않는다.
team.getMembers().add(member);

 

3. 양방향 연관관계 매핑 주의할 점

1) 연관관계 양쪽에 값을 세팅해 주어야 한다.

// team 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team); // team 1차 캐시에 저장

// member 저장
Member member = new Member();
member.setTeam(team);
em.persist(member); // member 1차 캐시에 저장

// 추가코드 필요...

// 1차 캐시에서 조회
// team.members 리스트는 1차 캐시에 비어있는 상태
Team findTeam = em.find(Team.class, team.getId());
System.out.println("size: " + findTeam.getMembers().size()); // 0

1. 주인이 아닌 Team.members 리스트에도 함께 저장해 주거나

2. 1차 캐시를 DB에 반영하고 영속성 컨텍스트를 초기화해 em.find() 호출시 DB에서 조회할 수 있도록 해주어야 한다.

// 추가코드

// 1
team.getMembers().add(member);

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

DB에서 team을 조회할 때 2개의 쿼리 실행

   select team where team_id = ?

   select member where team_id = ?

 

2) 1번을 간편하게 해결하기 위해 연관관계 편의 메소드를 활용하자.

기존 setter와 차별을 두도록 새로운 메소드를 만들자.

@Entity
public class Member {
    // 생략 ...

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
	
    // 연관관계 편의 메소드
    public changeTeam(Team team) {
      if(this.team != null) {
      	this.team.getMembers().remove(this);
      }
      this.team = team;
      this.team.getMembers().add(this);
    }
}

// 사용
member.changeTeam(team);

 

3) 양방향 매핑시 무한루프를 조심하자.

 > toString(), lombok

@Entity
public class Team {
  // 생략 ...
  
  @OneToMany(mappedBy = "team")
  List<Member> members = new ArrayList<Member>();
  
  // members에서 member조회
  // member에서  member.team조회
  // team에서    members조회
  // members에서 member조회
  // ...
  @Override
  public String toString() {
      return "Team{" +
            "id=" + id +
              ", name=" + name +
              ", members=" + members +
              '}';
  }
}

> JSON 생성 라이브러리

spring의 controller의 response로 엔티티를 반환할 때

spring이 엔티티를 JSON형태로 변형해 반환하는 과정에서 무한루프 발생.

  (추가) 엔티티가 변경되면 controller api 또한 변경되기 때문에

--> DTO를 만들어 활용하자.

 

4) 정리

 > 단방향 매핑으로 1차 설계를 완료하자. (객체-테이블 매핑 완료)

 > 양방향 매핑은 객체 그래프 탐색이 필요한 경우에 추가하자. (테이블에 영향을 주지 않음)

 

 

 

 

출처

https://www.inflearn.com/course/ORM-JPA-Basic 자바 ORM 표준 JPA 프로그래밍 - 기본편(김영한)

728x90

'ORM > JPA' 카테고리의 다른 글

[JPA] 6. 프록시와 연관관계 관리  (0) 2021.12.14
[JPA] 5. 고급 매핑  (0) 2021.12.01
[JPA] 4. 다양한 연관관계 매핑  (0) 2021.11.29
[JPA] 2. 엔티티 매핑  (0) 2021.11.07
[JPA] 1. 내부동작방식  (0) 2021.11.06