jacojang

C++, Java, Python, Javascript를 주로 사용하고 요즘은 Functional Programming에 관심이 많은 평범한 개발자 입니다. 음악을 사랑하고 그보다 더 딸을 사랑하는 딸바보 아빠입니다.~!!

JPA - 5장 - 연관관계 매핑 기초

05 Dec 2016 » jpa, java, hibernate

※ 이 문서는 개인적인 필요에 의해 JPA 프로그래밍 책을 요약/추가 한 내용입니다.

단방향 연관관계

연관관계중에서는 다대일(N:1)을 가정 먼저 이해 하고 넘어가야 한다. 회원과 팀의 관계가 그에 해당 한다.

회원과 팀이 있다. 회원은 하나의 팀에만 속할 수 있다.

---------           0..1  ----------
Member    --------------> Team
---------                 ----------
id                        id
Team team                 name
username
---------                 ----------
객체 연관관계

Member 객체는 Member.team 필드로 팀객체와 연관 관계를 맺고 있고 이는 단방향 관계이다. 즉 Member는 team 필드로 팀을 알수 있지만 Team은 Member를 알아 낼 수 없다.

테이블 연관관계

반면 테이블은 외래 키를 이용해서 양방향으로 조회 할 수 있다. 즉, 외래키를 이용해서 서로 JOIN해주면 양방향의 관계를 알아 낼 수 있다.

객체 관계 매핑

@Entity
public class Member {
  @Id
  @Column(name = "MEMBER_ID")
  private String id;

  private String username;

  @ManyToOne
  @JoinColumn(name = "TEAM_ID")
  private Team team;

  // Getter, Setter...
}

@Entity
public class Team {
  @Id
  @Column(name = "TEAM_ID")
  private String id;

  private String name;

  // Getter, Setter...
}
@ManyToOne

말그 대로 N:1의 관계를 라는 매핑 정보 이다.

속성
  • optional
    • 기본은 true인데 false로 설정하면 연관된 Entity가 꼭 있어야 한다.
  • fetch
    • 8장에서…
    • FetchType.EAGER
    • FetchType.LAZY
  • cascade
    • 영속성 전이 기능을 사용
  • targetEntity
    • 연관된 엔티티의 타입 정보를 설정
    @OneToMany
    private List<Member> members  // 제네릭으로 타입정보를 알수 있다.

    @OneToMany(targetEntity=Member.class)
    private List member // 제네릭이 없으면 타입 정보를 알수 없다.
    
@JoinColumn(name=”TEAM_ID”)

JoinColumn 은 외래키를 매핑할 때 사용한다.

속성
  • name
    • 매핑할 외래 키 이름
  • referencedColumnName
    • 외래 키가 참조하는 대상 테이블의 컬럼명
  • foreignKey(DDL)
    • 외래키 제약조건을 직접 설정하기위한 속성이다.
  • unique
  • nullable
  • insertable
  • updatable
  • columnDefinition
  • table
    • @Column속성과 같다.

연관관계 사용

저장

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

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

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

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

조회

연관관계가 있는 엔티티를 조회 하는 방법은 “객체 그래프 탐색” 방법과 “객체지향 쿼리 사용” 이 있다.

객체 그래프 탐색
Member member = em.find(Member.class, "member1");
Team team = member.getTeam();   // <---- 이것이 객체 그래프 탐색이다.
객체지향 쿼리 사용

객체지향 쿼리인 JPQL을 이용한 방법이다.

String jpql = "select m from Member m join m.team t where t.name = :teamName";

List<Member> resultList = em.createQuery(jpql, Member.class)
                          .setParmeter("teamName", "팀1")
                          .getResultList();

수정

em.update()와 같은 함수는 없다. 단순히 아래와 같이 처리하면 된다.

Team team2 = new Team("team2", "팀2");
em.persist(team2);

Member member = em.fine(Member.class, "member1");
member.setTeam(team2);

연관 관계 제거

아래와 같이 null로 처리해 주면 관계는 삭제 된다.

Member member = em.fine(Member.class, "member1");
member.setTeam(null);

연관된 엔티티 삭제

연관된 엔티티를 삭제 하려면 해당 엔티티를 사용하는 모은 엔티티의 연관 관계를 제거 해줘야 한다.

member1.setTeam(null);
member2.setTeam(null);
em.remove(team);

양방향 연관관계

단방향에서는 “회원” –> “팀” 으로만 확인이 가능했다면 양방향에서는 “팀” –> “회원” 으로도 확인이 가능해야 한다. RDB의 Table에서의 관계는 양방향과 단방향이 모두 동일하다.(FK로 서로 교차 검색이 가능하다)

@Entity
public class Team {
  @Id
  @Column(name = "TEAM_ID")
  private String id;

  private String name;

  // 추가
  @OneToMany(mappedBy = "team")
  private List<Member> members = new ArrayList<Member>();

  // Getter, Setter...
}

mappedBy는 양방향 매핑을 할때 반대쪽에 매핑할 필드의 이름이다.

연관관계의 주인

mappedBy는 왜? 필요할까??

  • 객체에서 양방향 관계라는 것은 없다. -> 2개의 단방향 관계가 있는 것이다.
  • mappedBy는 연관관계의 주인을 정해주는 작업이다.
  • 연관관계의 주인은 외래키가 존재하는 엔티티가 된다.
  • 즉, 연관관계 주인 만이 연관관계를 갱신할 수 있고, 반대편(inverse, non-owning side)는 읽기만 가능하다.

양방향 연관관계 저장 및 주의할 점

예제 에서는 Member.team 연관관계의 주인이므로 아래 코드의 내용에 주의 해줘야한다.

// 정상적인 연관관계의 설정은 Member 에서 이루어져야 한다.
Team team1 = new Team("team1", "팀1");
em.persist(team1);

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

// Team에서 연관관계를 업데이트 하려고 시도하는것은 무시된다.
Member member2 = new Member("member2", "회원2");
em.persist(member2);

Team team1 = new Team("team1", "팀1");
team1.getMembers().add(member2);
em.persist(team1);

하지만 순수 객체로 따져 보면 위 예제의 정상과 비정상 예제의 내용이 모두 실행되어야 정상적으로 동작하게 된다. 그래서 Member의 setTeam을 아래와 같이 수정해 주면 순수 객체에서도 정상동작 하게 된다.

public void setTeam(Team team){
  // 기존팀 관계 제거
  //   -> 영속성 컨텍스트가 새로 시작되면 문제가 없지만 영속성 컨텍스트를 계속 사용중이라면
  //      문제가 발생 할 수 있다.
  if (this.team != null){
      this.team.getMembers().remove(this);
  }
  this.team = team;
  team.getMembers().add(this);
}