JPA 연관관계

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

  • 테이블 연관관계에서는 언제나 양방향 탐색이 가능하다. A 테이블이 B 테이블의 식별자를 외래키로 가지고 B 테이블을 참조한다면, B 테이블도 반대로, A 테이블의 외래키 컬럼을 이용해 B의 특정 식별자와 연관관계가 있는 A 테이블 레코드를 검색할 수 있다.

  • 참조를 통한 객체의 연관관계는 언제나 단방향이다. 객체는 자신이 의존하는 객체에 대해서만 참조할 수 있고, 자신을 의존하고 있는 객체에 대해서는 알 수 없다. 객체에서 양방향 연관관계는 단방향 연관관계가 두 개인 것이다.

단방향 연관관계

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

    private String username;

    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
    
    ...
}
@Entity
public class Team {
    @Id
    @Column(name = "TEAM_ID")
    private String id;
    
    private String name;

    ...
}

@JoinColumn

외래키를 매핑할 때 사용한다. (생략 가능)

속성 기능 기본값
name 매핑할 외래키 이름 (필드명)_(참조테이블 기본키 컬럼명)
referencedColumnName 외래키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본키 컬럼명
foreignKey(DDL) 외래키 제약조건을 직접 지정. 테이블 생성시에만 사용함.  
unique, nullable, insertable, updatable, columnDefinition, table @Column 속성과 동일함.  

@ManyToOne

다대일(N:1) 관계 매핑

속성 기능 기본값
optional false로 설정 시 연관된 엔티티가 항상 존재해야 함. true
fetch 글로벌 패치 전략을 설정 @ManyToOne=FetchType.EAGER
@OneToMany=FetchType.LAZY
cascade 엔티티의 상태 변화를 연관된 자식들에게 전이  
orphanRemoval 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제됨. false

연관관계 사용

저장

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

Team team = new Team(...);
em.persist(team);

Member member = new Member(...);
member.setTeam(team);   // 영속화 된 team과 연관관계 설정
em.persist(member);

조회

  • 객체 그래프 탐색
Member member = em.find(Member.class, "member");
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)
                        .setParameter("teamName", "승리팀")
                        .getResultList();

수정

변경 감지 기능을 통해 트랜잭션 커밋시 자동 플러시로 update 기능 수행한다.

Team team = new Team(...);
em.persist(team);

Member member = em.find(Member.class, "member");
member.setTeam(team);

연관관계 제거

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

연관된 엔티티 삭제

연관관계를 제거 후 삭제 가능하다.

Team team = new Team(...);

...

Member member = em.find(Member.class, "member");
member.setTeam(null);
em.remove(team);

양방향 연관관계

@Entity
public class Member {
    ...

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

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

    ...
}

컬렉션 조회

반대 방향으로 객체 그래프를 탐색한다.

Team team = em.find(Team.class, "team");

List<Member> members = team.getMembers(); //(팀 -> 회원)

연관관계의 주인

  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다. 즉, 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.

  • 외래키 컬럼을 가지는 엔티티를 연관관계의 주인으로 설정하자. 이 엔티티가 매핑되는 테이블은 참조하는 엔티티의 식별자를 외래키 컬럼으로 가지게 된다.

  • 연관관계의 주인만이 DB와 매핑하고 외래키를 관리(등록, 수정, 삭제)할 수 있다. 주인이 아닌 쪽은 읽기만 가능하다.

mappedBy 속성

연관관계의 주인에는 mappedBy 속성을 사용하지 않고, 주인이 아닌 쪽에 mappedBy 속성의 값으로 연관관계 주인을 지정한다. 연관관계의 주인을 정한다는 것은 외래키 관리자를 선택하는 것이다.

양방향 연관관계의 주의점

순수 객체 상태를 고려해서 항상 연관관계의 양쪽에 값을 설정하자. 연관관계의 주인이 아닌 엔티티에만 값을 입력하는 경우 DB에 외래키 값이 정상적으로 저장되지 않는다.

잘못된 예제

Team team = new Team("team", "승리팀");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");

team.getMembers().add(member1);
team.getMembers().add(member2);

양방향 모두 설정한 예제

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

Team team = new Team("team", "승리팀");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");

member1.setTeam(team);
team.getMembers().add(member1);

member2.setTeam(team);
team.getMembers().add(member2);

List<Member> members = team1.getMembers();

연관관계 편의 메서드

편의 메서드를 생성해두면 실수를 줄일 수 있다.

public class Member {
    private Team team;

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

단방향 매핑만으로 테이블과 객체 연관관계 매핑은 이미 완료된다. 양방향 연관관계는 반대방향으로 객체 그래프 탐색 기능이 추가된 것 뿐이다.
단방향 매핑만 잘 정의해놓으면 양방향 매핑은 필요할 때 추가해도 된다(테이블에 영향을 주지 않음).

Categories:

Updated:

Comments