DB/JPA

[📗자바 ORM 표준 JPA 프로그래밍] 단방향 연관관계 매핑하기

개발자 May 2024. 11. 12.

엔티티 간 연관관계 개요

엔티티들은 다른 엔티티와 연관관계를 갖는 경우가 많다. 예를 들어, 주문 엔티티는 상품 엔티티와 연관되고, 상품 엔티티는 또 다른 엔티티(카테고리, 재고 등)와 연관될 수 있다.

객체는 참조를 사용해 관계를 맺는 반면, 데이터베이스는 외래 키를 사용해 관계를 설정한다. 이러한 차이로 인해 ORM에서 객체 간의 연관관계를 테이블 연관관계에 매핑하는 작업이 필요하며, 이를 적절히 이해하는 것이 중요하다.


주요 개념

  1. 방향 (Direction): 단방향과 양방향으로 구분된다.
    • 단방향: 엔티티가 하나의 방향으로만 다른 엔티티를 참조한다. 예를 들어, 회원 → 팀의 관계가 단방향 관계다.
    • 양방향: 양쪽 엔티티가 서로 참조한다. 회원 ↔ 팀의 관계가 양방향 관계이며, 양방향 관계에서는 양쪽에 참조 필드를 두어야 한다.
  2. 다중성 (Multiplicity): 관계의 유형을 나타내며, 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)로 구분된다.
    • 예: 회원 - 팀 관계에서 여러 회원이 하나의 팀에 소속되므로 다대일(N:1) 관계이다. 반대로 팀 - 회원일대다(1:N) 관계이다.
  3. 연관관계의 주인 (Owner): 양방향 연관관계에서 외래 키를 관리하는 주체를 지정해야 하며, 이를 연관관계의 주인이라 한다.

5.1 단방향 연관관계

가장 기본적인 관계로, 예를 들어 회원과 팀이 다대일(N:1) 단방향 관계를 가진다고 가정한다.

  • 회원은 하나의 팀에 소속될 수 있다.
  • 회원 객체는 Member.team 필드를 통해 팀 객체와 단방향 관계를 맺는다.
  • 회원은 팀을 알지만, 팀은 회원을 알지 못하는 관계다.

객체 연관관계

  • 객체 연관관계: 회원 객체는 Member.team 필드로 팀 객체를 참조하며, 이를 통해 팀 정보를 얻을 수 있다.

테이블 연관관계

  • 테이블 연관관계: 데이터베이스에서는 회원 테이블의 TEAM_ID 외래 키를 통해 팀 테이블과 연관관계를 맺는다. 외래 키가 포함된 테이블을 기준으로 관계를 정의하며, 조인을 통해 양방향으로 조회할 수 있다.

객체 관계 매핑 예제

객체와 테이블 간의 연관관계를 다음과 같이 매핑할 수 있다.

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

    private String username;

    // 연관관계 매핑
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    public void setTeam(Team team) {
        this.team = team;
    }
    // getters, setters 생략
}

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

    private String name;
    // getters, setters 생략
}
  • @ManyToOne: 회원과 팀이 다대일(N:1) 관계임을 나타내며, @JoinColumn을 통해 외래 키인 TEAM_ID를 설정한다.
  • @JoinColumn(name="TEAM_ID"): 조인 컬럼을 명시하여 외래 키로 매핑할 필드를 지정한다.

5.2 연관관계 사용

저장

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

public void testSave() {
    // 팀1 저장
    Team team1 = new Team();
    team1.setId("team1");
    team1.setName("팀1");
    em.persist(team1);

    // 회원1 저장
    Member member1 = new Member();
    member1.setId("member1");
    member1.setUsername("회원1");
    member1.setTeam(team1); // 연관관계 설정
    em.persist(member1);
}

JPA는 회원 엔티티가 참조하는 팀 엔티티의 식별자(id)를 외래 키로 사용하여 적절한 INSERT 쿼리를 생성한다.

조회

엔티티 간 연관관계를 조회하는 방법에는 객체 그래프 탐색JPQL이 있다.

  1. 객체 그래프 탐색: 객체 연관관계를 통해 member1.getTeam()으로 팀 정보를 탐색할 수 있다.
  2. JPQL 사용: JPQL에서 member1.team으로 팀과 조인하여 원하는 정보를 조회할 수 있다.
// 객체 그래프 탐색
Team team = member1.getTeam();

// JPQL 사용
String jpql = "SELECT m FROM Member m JOIN m.team t WHERE t.name = :teamName";
List<Member> result = em.createQuery(jpql, Member.class)
                        .setParameter("teamName", "팀1")
                        .getResultList();

수정

연관관계를 수정할 때는 단순히 새로운 엔티티를 참조하도록 설정하면 된다. 예를 들어, 팀1 소속이던 회원1을 팀2 소속으로 변경하려면 다음과 같이 설정한다.

public void updateRelation(EntityManager em) {
    Team team2 = new Team();
    team2.setId("team2");
    team2.setName("팀2");
    em.persist(team2);

    Member member = em.find(Member.class, "member1");
    member.setTeam(team2); // 새로운 팀 설정
}

JPA는 트랜잭션 커밋 시 변경 감지 기능을 통해 자동으로 UPDATE 쿼리를 생성하여 DB에 반영한다.


연관관계 제거

연관관계를 제거하려면 단순히 참조를 null로 설정하면 된다.

member.setTeam(null); // 연관관계 제거

이 코드가 실행되면 SET TEAM_ID = null 쿼리가 발생하여 외래 키가 제거된다.


삭제

연관된 엔티티를 삭제할 때는 외래 키 제약 조건 때문에 연관관계를 먼저 제거해야 한다. 예를 들어, 회원이 소속된 팀을 삭제하려면 연관된 회원의 팀 정보를 먼저 제거해야 한다.

public void deleteTeam(EntityManager em) {
    Member member = em.find(Member.class, "member1");
    member.setTeam(null); // 연관관계 제거

    Team team = em.find(Team.class, "team1");
    em.remove(team); // 팀 삭제
}

연관관계를 제거하지 않으면 외래 키 제약조건으로 인해 데이터베이스에서 오류가 발생한다.


연관관계 매핑을 위한 주요 어노테이션

  1. @ManyToOne: 다대일(N:1) 관계를 매핑하며, 예를 들어 회원 - 팀 관계에서 여러 회원이 하나의 팀에 속할 때 사용한다.
    • optional: false로 설정하면 연관된 엔티티가 항상 존재해야 한다.
    • fetch: 글로벌 페치 전략을 설정한다. (ManyToOne의 기본값은 즉시 로딩(EAGER))
    • cascade: 영속성 전이 기능을 설정한다.
  2. @JoinColumn: 외래 키 컬럼을 지정하며, name 속성을 통해 매핑할 외래 키의 컬럼명을 설정한다.

연관관계 매핑 요약

  • 객체는 참조로 연관관계를 맺기 때문에 단방향 관계가 기본이다. 양방향으로 설정하려면 서로 참조 필드를 추가하여 단방향 관계 2개를 만들어야 한다.
  • 테이블은 외래 키를 통해 양방향 관계를 제공하며, 조인을 통해 어느 방향에서든 관계를 조회할 수 있다.

Reference

[도서]자바 ORM 표준 JPA 프로그래밍(김영한 저)

댓글