프로그래밍/Java

[스터디10] 05. 연관관계 매핑 기초

mandus 2025. 7. 19. 19:13

목차

    5장. 연관관계 매핑 기초

    - 객체의 참조와 테이블의 외래키를 매핑하는 것이 목표
    - 방향(Direction): 단방향, 양방향
    - 다중성(Multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
    - 연관관계의 주인(owner): 객체를 양방향 연관관계로 만들면 정해야 한다.

    5.1 단방향 연관관계

    • 객체 연관관계와 테이블 연관관계 차이점:
      • 참조를 통한 객체의 연관관계는 단방향이다. 객체간 연관관계를 양방향으로 만들려면 반대쪽에도 필드를 추가해서 참조를 보관해야 한다. 정확히는 서로 다른 단방향 관계 2개다.
      • 외래키를 사용하는 테이블의 연관관계는 양방향이다.
    • 객체참조(주소)로 연관관계를 맺고 테이블외래키로 연관관계를 맺는다. 연관된 데이터를 조회할 때 객체는 참조를 사용하지만 테이블은 조인을 사용한다.
    // 단방향 연관관계
    class A {
        B b;
    }
    class B {}
    
    // 양방향 연관관계
    class A {
        B b;
    }
    class B {
        A a;
    }

    5.1.1 순수한 객체 연관관계

    • 객체 그래프 탐색: 객체는 참조를 탐색해서 연관관계를 탐색할 수 있다.
    Team findTeam = member1.getTeam();

    5.1.2 테이블 연관관계

    • 조인: 데이터베이스는 외래키를 사용해서 연관관계를 탐색할 수 있다.
    SELECT T.*
    FROM MEMBER M
        JOIN TEAM T ON M.TEAM_ID = T.ID
    WHERE M.MEMBER_ID = 'member1'

    5.1.3 객체 관계 매핑

    • 객체 연관관계: 회원 객체의 Member.team 필드 사용
    • 테이블 연관관계: 회원 테이블의 MEMBER.TEAM_ID 외래키 칼럼 사용
    // 연관관계 매핑
    @ManyToOne	// 다대일 관계
    @JoinColumn(name="TEAM_ID")	// 외래키를 매핑할 때 사용, 생략 가능
    private Team team;

    다대일(@ManyToOne)과 비슷한 일대일(@OneToOne) 관계도 있다. 반대편이 일대다 관계면 다대일을 사용하고, 반대편이 일대일 관계면 일대일을 사용한다.

    5.2 연관관계 사용

    5.2.1 저장

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

    Team team1 = new Team("team1", "팀1");
    em.persist(team1);
    
    Member member1 = new Member("member1", "회원1");
    member1.setTeam(team1);	// 회원1 -> 팀1 참조
    em.persist(member1);	// 저장

    5.2.2 조회

    1. 객체 그래프 탐색(객체 연관관계를 사용한 조회)
    2. 객체지향 쿼리 사용(JPQL)
    Member member = em.find(Member.class, "member1");
    Team team = member.getTeam();	// 객체 그래프 탐색
    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", "팀1");
        .getResultList();

    5.2.3 수정

    수정은 em.update() 같은 메소드가 없다. 엔티티의 값을 변경하면 트랜잭션을 커밋할 때 플러시가 일어나면서 변경 감지 기능이 작동하고, 변경사항을 데이터베이스에 자동 반영한다.

    Team team2 = new Team("team2", "팀2");
    em.persist(team2);
    
    // 회원1에 새로운 팀2 설정
    Member member = em.find(Member.class, "member1");
    member.setTeam(team2);

    5.2.4 연관관계 제거

    Member member1 = em.find(Member.class, "member1");
    member1.setTeam(null);	// 연관관계 제거

    5.2.5 연관된 엔티티 삭제

    연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 한다. 그렇지 않으면 외래키 제약조건으로 데이터베이스에서 오류가 발생한다.

    member1.setTeam(null);	// 회원1 연관관계 제거
    member2.setTeam(null);	// 회원2 연관관계 제거
    em.remove(team);	// 팀 삭제

    5.3 양방향 연관관계

    • 객체 연관관계: 일대다 관계는 여러 건과 연관관계를 맺을 수 있으므로 컬렉션을 사용해야 한다.
    • 테이블 연관관계: 데이터베이스 테이블은 외래키 하나로 양방향으로 조회할 수 있다.

    5.3.1 양방향 연관관계 매핑

    @Entity
    public class Member {
        
        // ...
        
        // 연관관계 설정
        public void setTeam(Team team) {
            this.team = team;
        }
        
        // Getter, Setter...
    }
    
    @Entity
    public class Team {
        
        // ...
        
        @OneToMany(mappedBy = "team")
        private List<Member> members = new ArrayList<Member>();
        
        // Getter, Setter...
    }

    5.3.2 일대다 컬렉션 조회

    Team team = em.find(Team.class, "team1");
    List<Member> members = team.getMembers();	// (팀 -> 회원) 객체 그래프 탐색

    5.4 연관관계의 주인

    • 객체에는 양방향 연관관계가 없고, 서로 다른 단방향 연관관계 2개를 양방향인 것처럼 보이게 하는 것이다. 테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.
    • 엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래키를 관리한다. 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래키는 하나이므로 차이가 발생한다. 따라서 JPA는 연관관계의 주인을 정해서 테이블의 외래키를 관리한다.

    5.4.1 양방향 매핑의 규칙: 연관관계의 주인

    • 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래키를 관리(등록, 수정, 삭제)할 수 있다. 주인이 아닌 쪽은 읽기만 할 수 있다.
      • 주인은 mappedBy 속성을 사용하지 않는다.
      • 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.
    구분 연관관계의 주인 (Owner) 주인이 아닌 쪽 (Inverse)
    외래 키 관리 O (등록, 수정) X (읽기만 가능)
    mappedBy 속성 사용 안 함 사용함 (주인 필드명 지정)
    대표 어노테이션 @ManyToOne, @OneToOne @OneToMany, @OneToOne

    5.4.2 연관관계의 주인은 외래키가 있는 곳

    • 연관관계의 주인은 테이블에 외래키가 있는 곳으로 정한다. 예) 회원 테이블이 외래키를 가지고 있으므로 Member.team이 주인이 되고, Team.members에는 mappedBy="team" 속성을 사용해서 주인이 아님을 설정한다.
    • 데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래키를 가진다. 따라서 @ManyToOne에는 mappedBy 속성이 없다.

    5.5 양방향 연관관계 저장

    // 팀1 저장
    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);

    TEAM_ID 외래키에 팀의 기본키 값이 저장되어 있다. 양방향 연관관계는 연관관계의 주인이 외래키를 관리한다.

    team1.getMembers().add(member1);	// 무시(연관관계의 주인X)
    team1.getMembers().add(member2);	// 무시(연관관계의 주인X)
    
    member1.setTeam(team1);		// 연관관계 설정(연관관계의 주인O)
    member2.setTeam(team1);		// 연관관계 설정(연관관계의 주인O)

    5.6 양방향 연관관계의 주의점

    연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌 곳에 값을 입력하는 실수를 조심해야 한다. team1.getMembers().add(member1);처럼 TEAM_ID에 null 값이 입력되어 있을 수 있다.

    5.6.1 순수한 객체까지 고려한 양방향 연관관계

    객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전하다. JPA를 사용하지 않는 순수한 객체 상태에서 문제가 발생할 수 있기 때문이다.

    5.6.2 연관관계 편의 메소드

    양방향 관계에서 두 코드는 하나인 것처럼 사용하는 것이 안전하다.

    public void setTeam(Team team) {	// 메소드 하나로 양방향 관계 모두 설정
        this.team = team;
        team.getMembers().add(this);
    }

    5.6.3 연관관계 편의 메소드 작성 시 주의사항

    연관관계를 변경할 때는 기존 팀이 있으면 기존 팀과 회원의 연관관계를 삭제하는 코드를 추가해야 한다.

    public void setTeam(Team team) {
        
        // 기존 팀과 관계 제거
        if (this.team != null) {
            this.team.getMembers().remove(this);
        }
        this.team = team;
        team.getMembers().add(this);
    }

    5.7 정리

    • 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다.
    • 단방향을 양방향으로 만들면 반대 방향으로 객체 그래프 탐색 기능이 추가된다.
    • 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.

     

    ✔️ 복습하기
    1. 객체 연관관계와 테이블 연관관계의 차이점은?
    2. 객체 연관관계를 양방향으로 만들려면?
    3. 연관관계의 주인이란?
    4. JPA에서 양방향 연관관계를 설정할 때 연관관계의 주인을 정하는 이유는?

     

    이 글은 『 자바 ORM 표준 JPA 프로그래밍』 책을 학습한 내용을 정리한 것입니다.