[자바 ORM 표준 JPA 프로그래밍] 5. 연관관계 매핑 기초

2022. 5. 13. 01:54Web/Spring

반응형

velog에서 옮겨온 글 입니다.

원본 날짜: 2022-03-16T10:55:24.003Z

 

김영한님의 '자바 ORM 표준 JPA 프로그래밍' 서적을 읽고 정리한 글 입니다.

핵심 키워드

  • 방향(Direction)
    • 단방향, 양방향
  • 다중성(Multiplicity)
    • 다대일, 일대다, 일대일, 다대다
  • 연관 관계의 주인(Owner)
    • 객체 양방향 연관 관계는 관리하는 주인이 필요하다.

단방향 연관관계

@ManyToOne
@JoinColumn(name = "TEAM_ID")    // join 하기 위한 FK
private Team team;

@JoinColumn 을 생략하면 외래 키를 찾을 때 기본 전략을 사용한다. (기본 전략: 필드명 + _ + 참조하는 테이블의 컬럼명 )

// 팀 저장
Team team1 = new Team("team1", "팀1");
em.persist(team1);

// 회원1 저장
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);

// 회원2 저장
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1);
em.persist(member2);

JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.

연관된 엔티티 삭제

member1.setTeam(null);
member2.setTeam(null);
em.remove(team1);
  • 연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제고하고 삭제해야 한다.
  • 그렇지 않으면 외래 키 제약조건으로 인해, DB에서 오류가 발생한다.

객체 연관관계 vs 테이블 연관관계

  • 객체는 참조(주소)로 연관관계를 맺는다.
  • 테이블은 외래 키로 연관관계를 맺는다.
  • 참조를 사용하는 객체의 연관관계는 단방향이다.
  • 외래 키를 사용하는 테이블의 연관관계는 양방향이다.
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.

양방향 연관관계

  • 양방향으로 한다고 해도 테이블 연관 관계는 변한 것이 없다.
  • 문제는 객체다. TeamMember 를 넣어줘야만 접근이 가능하다.
@OneToMany(mappedBy = "team")    // Member 엔티티의 team 변수와 연결되어 있음을 의미
List<Member> members = new ArrayList<Member>();

연관관계의 주인

  • 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래키는 하나다. 따라서 둘 사이에 차이가 발생한다.
  • 그래서 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리해야 하는데 이것을 연관관계의 주인(Owner)이라 한다.
  • 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 주인을 지정해야 한다.

연관관계의 주인만이 DB 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다. 주인이 아닌 쪽은 읽기만 할 수 있다. 즉, 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것이다.

DB 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래 키를 가진다. 따라서 @ManyToOne 에는 mappedBy 속성이 없다.

양방향 연관관계의 주의점

// 회원1 저장
Member member1 = new Member("member1", "회원1");
em.persist(member1);

// 회원2 저장
Member member2 = new Member("member2", "회원2");
em.persist(member2);

Team team1 = new Team("team1", "팀1");
// 주인이 아닌 곳만 연관관계 설정
team1.getMembers().add(member1);
team1.getMembers().add(member2);

em.persist(team1);
  • 회원을 조회한 결과는 두 회원 모두 TEAM_IDnull 값이 입력되어 있다.
  • 왜냐하면 연관관계의 주인이 아닌 Team.members 에만 값을 저장했기 때문이다.

객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다.

연관관계 편의 메소드

  • 양쪽 방향 모두 값을 입력하는 메소드를 각각 호출하다 보면 실수로 하나만 호출해서 양방향이 깨질 수 있다.
  • 그래서 메소드 하나로 양방향 관계를 모두 설정하도록 변경할 수 있다.
public class Member {
    ...

    public void setTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
      }
}
  • 연관관계를 변경할 때는 기존 팀이 있으면 기존 팀과 회원의 연관관계를 삭제하는 코드를 추가해야 한다.
public class Member {
    ...

    public void setTeam(Team team) {
        if (this.team != null) {
            this.team.getMembers().remove(this);
        }
        this.team = team;
        team.getMembers().add(this);
      }
}
반응형