소나무나무 2021. 7. 22. 21:30

연관관계가 필요한 이유

  • 클래스에서 참조 대신에 외래키 식별자를 직접 다룸(teamId)
@Entity
public class Member {
     @Id @GeneratedValue
     private Long id;
     @Column(name = "USERNAME")
     private String name;
     @Column(name = "TEAM_ID")
     private Long teamId; // 참조없이 외래키 사용
     …
}

@Entity
public class Team {
     @Id @GeneratedValue
     private Long id;
     private String name;
     …
}
//팀 저장
 Team team = new Team();
 team.setName("TeamA");
 em.persist(team);
 //회원 저장
 Member member = new Member();
 member.setName("member1");
 member.setTeamId(team.getId()); // DIP(의존관계 역전) 위반!
 em.persist(member);
//조회
Member findMember = em.find(Member.class, member.getId());
//연관관계가 없음
Team findTeam = em.find(Team.class, team.getId());
  • 테이블은 외래키, 객체는 참조를 사용해서 연관 객체를 찾음
  • 객체를 테이블 중심으로 모델링하면 객체지향적 코드를 만들 수 없음

단방향 연관관계

  • 객체지향 연관관계를 사용함
  • 객체와 테이블을 ORM이 자동으로 매핑함
@Entity
 public class Member {

    @Id @GeneratedValue
     private Long id;

     @Column(name = "USERNAME")
     private String name;

     @ManyToOne // 클래스 기준으로 연관관계 매핑
     @JoinColumn(name = "TEAM_ID") // 조인할 컬럼 이름
     private Team team; // 참조 객체
 …

//조회
 Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
 Team findTeam = findMember.getTeam();

양방향 매핑 관계

  • 객체 연관관계는 서로 다른 단방향 관계 2개라고 보면 됨
    • Member → Team 연관관계 1개(단방향)
    • Team → Member 연관관계 1개(단방향)
  • 테이블 연관관계는 외래키로 공유하는 방식이여서 양방향임
    • Member(TEAM_ID) ↔ Team(TEAM_ID)
@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team") // 읽기 전용
    private List<Member> members = new ArrayList<>();
  • 연관관계 어노테이션을 주입하면 아래 코드처럼 양방향 조회가 가능하다
//조회
 Team findTeam = em.find(Team.class, team.getId());
//역방향 조회
 int memberSize = findTeam.getMembers().size(); 

연관관계의 주인

  • DB에 값을 업데이트할 주인 1개를 지정하지 않으면 두 객체에 값을 넣었을 때 ORM이 어떤 값을 넣어야할지 모르기 때문에 1개는 주인, 1개는 읽기 전용으로 지정해야함

  • 양방향 매핑 규칙

    • 객체의 두 관계중 하나를 연관관계의 주인으로 지정함

    • 연관관계의 주인만 외래키를 관리(등록, 수정)

    • 주인이 아니면 mappedBy 속성으로 값을 읽기만 하도록 함(주인은 지정 X)

    • 외래키가 있는 곳을 주인을 정하자

      다대일, 일대다 관계에선 다 쪽이 외래키를 갖고 있기 때문에 다 쪽이 연관관계 주인이다. 그래서 @ManyToOne에선 mappedBy 속성을 사용할 수 없다.

양방향 연관관계 주의점

  • 연관관계 주인에 값을 입력해야 함
Team team = new Team();
 team.setName("TeamA");
 em.persist(team);

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

 //역방향(주인이 아닌 방향)만 연관관계 설정
 team.getMembers().add(member);
 em.persist(member);
  • 순수한 객체관계에서는 양쪽 다 값을 입력해야 되기 때문에 양쪽 값을 다 입력하는 것을 권장함
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
//연관관계의 주인에 값 설정
 member.setTeam(team); 
// 양쪽 값 모두 입력
team.getMembers().add(member);
em.persist(member);
  • 양쪽 값을 모두 입력할 때에는 연관관계 편의 메소드를 사용해서 입력하는 것을 권장함
    • 깜빡하고 코드를 안 적을 가능성 배제
    • setter를 change로 명시하여 중요한 메서드라는 것을 알려주자
@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

  public void changeTeam(Team team) {
      this.team = team;
      team.getMembers().add(this); // 양쪽 값 모두 입력
  }
  • toString(), lombok, JSON 생성 라이브러리를 쓸 때 무한 루프를 주의하자

정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완성
  • 양방향 매핑은 반대 방향 조회(객체 그래프 탐색)이 추가된 것일뿐(DB에 영향을 주지 않음)
  • JPQL에서 역방향으로 탐색할 일이 많아서 그 때 사용함
  • 단방향 매핑을 잘하고 양방향 매핑은 개발 단계에서 고민해도 늦지 않음