course/inflearn

[JPA 프로그래밍 기본편] 연관관계 매핑 기초

hjkim0502 2022. 9. 1. 23:11
객체를 테이블에 맞춰 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다
- 테이블은 외래 키로 조인을 사용해 연관된 테이블을 찾는다
- 객체는 참조를 사용해 연관된 객체를 찾는다

 

단방향 연관관계

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    @Column(name = "USERNAME")
    private String name;
    
    private int age;
    
    // @Column(name = "TEAM_ID")
    // private Long teamId;
    
    @ManyToOne // JPA에 다대일 연관관계가 있음을 알려줌
    @JoinColumn(name = "TEAM_ID") // 외래키 매핑
    private Team team; // 객체 참조
  • teamId 값이 아닌 객체 자체를 참조하고, 그 객체와 테이블의 외래키를 매핑한다
//객체 지향적인 코드 진행 가능

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

// 회원 저장
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);

// 조회
Member findMember = em.find(Member.class, member.getId());

Team findTeam = findMember.getTeam();

// 새로운 팀 B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 회원1에 새로운 팀 B 설정
member.setTeam(teamB);

 

양방향 연관관계

  • 테이블은 단방향과 관계도가 똑같다 -> 외래키 설정 하나로 양쪽 방향 모두 조인 가능 (단방향이란 것이 없음)
// Team 클래스에 컬렉션 추가
@OneToMany(mappedBy = "team") // 일대다 연관관계가 있음과 주인이 아님을 알려줌
private List<Member> members = new ArrayList<>(); // add할때 nullpoint 에외 없도록 ArrayList로 초기화
  • mappedBy와 연관관계 주인
    • 객체는 사실상 단방향 두개를 합쳐서 양방향이라고 부르는 것이기 때문에 그 양방향 관계에서 DB의 외래키를 관리하는 주인이 필요하다

  • 양방향이 되면 멤버가 속한 팀이 바뀔 때 멤버의 team필드가 바뀌어야하는지, 팀의 members필드가 바뀌어야하는지 결정하는 문제가 생긴다.

  • 외래키가 있는 MEMBER 테이블과 매핑된 Member 엔티티의 참조값(Member.team)을 주인으로 정하기!
  • 주인만이 외래 키를 관리(등록, 수정, 삭제) -> mappedBy 미사용
  • 주인이 아닌 쪽은 읽기만 가능 -> mappedBy 사용
    • Team.members에 아무리 변화를 줘바야 DB에 반영되는 것은 없고
    • Member.team에 변화를 줘야 DB에 반영된다
  • 결론! 다대일 양방향 관계에서는 다 쪽이 주인이 된다

주의점!

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

Member member = new Member();
member.setUsername("member1");

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

em.persist(member);
MEMBER_ID USERNAME TEAM_ID
2 member1 null
  • 연관관계 주인에 값을 입력하지 않아 DB에 외래키 값이 없다
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 연관관계 주인에 값 설정

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

em.persist(member);
MEMBER_ID USERNAME TEAM_ID
2 member1 1
  • 하지만 순수한 객체 관계를 고려하면 양쪽에 다 값을 설정해주는 것이 좋다
    • 주인이 아닌쪽에서 설정하지 않아 1차 캐시에 캐싱된 데이터를 불러왔을 때 값이 비어있을 수 있다
    • 테스트 코드 짤 때 좋다
member.setTeam(team); // 연관관계 주인에 값 설정

team.getMembers().add(member); // 주인이 아닌쪽에 값 설정
  • 위와 같이 하면서 실수로 누락할 수 있기 때문에 아래와 같은 연관관계 편의 메소드 생성하여 둘 중 하나만 사용
// Member 클래스
public void changeTeam(Team team) {
    this.team = team; // 주인에 값 설정
    team.getMembers().add(this); // 역방향 설정
}

// main 클래스
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");

member.changeTeam(team); //**

em.persist(member);

또는

// Team 클래스
public void addMember(Member member) {
    member.setTeam(this); // 주인에 값 설정
    members.add(member); // 역방향 설정
}

// main 클래스
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");

team.addMember(member); //**

em.persist(member);

 

  • 무한 루프 조심
    • 예) toString(), lombok, JSON 생성 라이브러리
      • 양쪽에서 IDE가 자동생성한 toString() 메소드 호출하여 무한으로 서로를 호출할 수 있음
    • 컨트롤러에서 엔티티 자체를 반환하지 말고 Dto 사용하기

정리

  • JPA 연관관계 매핑의 설계 단계에서 단방향으로만 매핑 완료하기 (일대다 관계에서 다 쪽에 필요한 세팅하는 것)
  • 이후 개발과정에서 추가로 역방향 조회가 필요하다 싶으면 그때 추가하면 됨 (mappedBy)

 

order

 

실전 예제

  • 객체 참조를 사용하도록 변경

// Member
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
    
// Order
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems = new ArrayList<>();
    
// OrderItem
    @ManyToOne
    @JoinColumn(name = "ORDER_ID")
    private Order order;

    @ManyToOne
    @JoinColumn(name = "ITEM_ID")
    private Item item;
    
// Item
// 변화없음

 

출처: 인프런 김영한님 JPA 프로그래밍 - 기본편