연관관계 매핑

2024. 10. 3. 17:06·Spring
728x90

ORM(Object Relational Mapping)에서 가장 어려운 부분은 객체 연관관계와 테이블 연관관계를 매핑하는 일이다.

 

연관관계 매핑을 위한 핵심 키워드

- 방향 : 한쪽만 참조하는 것을 단방향 관계라고 한다. 양쪽 모두 참조하는 것을 양방향 관계라고 한다. 방향은 객체관계에만 존재한다. 테이              블 관계는 항상 양방향이다.

- 다중성 : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)

- 연관관계의 주인 : 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.

 

단방향 연관관계

회원과 팀이 있다.

회원은 하나의 팀에 소속될 수 있다.

회원과 팀은 다대일 관계다.

 

객체 연관관계

회원 객체와 팀 객체는 단방향 관계다. Member.team필드를 통해서 팀을 알 수 있지만 반대로 팀은 회원을 알 수 없다.

 

테이블 연관관계

회원 테이블은 TEAM_ID 외래키로 팀 테이블과 연관관계를 맺는다.

회원테이블과 팀 테이블은 양방향 관계다.

 

객체 연관관계와 테이블 연관관계의 큰 차이

참조를 통한 연관관계는 언제나 단방향이다. 양방향으로 만들려면 반대쪽 필드에도 추가해서 참조를 보관해야 한다.

양쪽에서 참조하는 것을 양방향 연관관계라 한다. 정확히는 서로 다른 단방향 관계 2개인 것이다.

 

순수 객체 연관관계는 객체 그래프 탐색을 참조를 사용해서 할 수 있다.

테이블 연관관계는 조인을 외래키를 사용해서 할 수 있다.

 

 

@JoinColumn

외래키를 매핑할 떄 사용한다.

속성

- name : 매핑할 외래 키 이름

- referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명

- foreignKey(DDL) : 외래 키 제약조건을 직접 지정할 수 있다. 이 속성은 테이블을 생성할 때만 사용한다.

- unique, nullable, insertable, updatable, columnDefinition, table

 

@ManyToOne

다대일 관계에서 사용한다.

속성

- optional : false로 설정하면 연관된 엔티티가 항상 있어야 한다.

- fetch : 글로벌 페치 전략을 설정한다.

- cascade : 영속성 전이 기능을 사용한다.

- targetEntity : 연관된 엔티티의 타입 정보를 설정, 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.

 

사용 예시

@OneToMany
private List<Member> members;

@OneToMany(targetEntity=Member.class)
private List members;

 

연관관계 사용

저장

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

JPA는 참조한 객체의 식별자를 외래키로 사용해서 적절한 등록 쿼리를 실행한다.

조회

- 객체 그래프 탐색

- 객체지향 쿼리 사용(JPQL)

수정

단순히 불러온 엔티티의 값을 변경해두면 트랜잭션을 커밋할 떄 플러시가 일어나면 변경 감지 기능이 작동한다.

그러면 스냅샷과 영속성 컨텍스트의 내용을 비교해서 변경된 부분을 알아서 DB에 저장한다.

 

연관관계 제거

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

연관된 엔티티 삭제

연관된 엔티티를 삭제하려면 기본 연관관계를 먼저 제거하고 삭제해야 한다. 외래키 제약조건으로 DB에서 오류가 발생하지 않게 하기위해서이다.

 

양방향 연관관계

객체 연관관계는 일대다 관계에 대해서는 컬렉션을 사용해야 한다.

List, Collection, Set, Map 등을 사용해야 한다.

테이블의 경우는 외래키로 양방향 연관관계를 만들기 때문에 추가할 것이 없다.

@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;
    }
}

@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>();
}

mappedBy속성은 양방향 매핑일 때 사용한다. 반대쪽 매핑의 필드 이름을 값으로 주면 된다.

위의 예시의 경우는 Member에 Member.team이므로 team을 값으로 주었다.

 

연관관계의 주인

mappedBy 속성은 왜 필요한가?

객체에는 양방향 연관관계라는 것이 엄밀히 말하면 없다. 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 잘 묶어서 양방향인 것처러 보이게 할 뿐이다.

엔티티를 양방향으로 매핑하면 연관관계를 관리하는 포인트는 2곳으로 늘어난다.

즉, 객체를 참조는 둘인데 외래키는 하나인 것이다. 따라서 둘 사이에 차이가 발생한다.

두 객체 연관관계 중 어떤 것을 사용해서 테이블의 외래키를 관리할지 정하는 것이 연관관계의 주인이다.

 

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

주인은 mappedBy 속성을 사용하지 않는다.

주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.

 

그럼 누구를 주인으로 지정해야 하는가?

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

class Team {
    @OneToMany
    private List<Member> members = new ArrayList<Member>();
}

연관관계의 주인을 정하는 것은 외래키의 관리자를 정하는 것이다.

위의 예시에서는 Member테이블에 있는 TEAM_ID를 관리해야 한다.

Member.team을 주인으로 선택하면 자신의 테이블의 외래키를 관리하면 된다.

하지만 만약 Team의 members가 주인으로 선택된다면 물리적으로 다른 테이블의 외래키를 관리해야 한다.

관리해야 할 외래키인 TEAM_ID는 Member테이블에 있는데 members는 Team 테이블에 매핑되어 있기 때문이다.

 

따라서 연관관계의 주인은 외래키가 있는 곳이 되어야 한다.

위의 예시에서는 연관관계의 주인은 Member.team이다. 그래서 Team.members에 mappedBy로 연관관계의 주인인 team을 속성값으로 준다.

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

 

양방향 연관관계에서 주의할 점은 연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌 곳에만 값을 입력하여 DB에 값이 저장되지 않는 경우이다.

 

Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");

em.persist(member1);
em.persist(member2);

Team team = new Team("team","팀");
team.getMembers().add(member1);
team.getMembers().add(member2);

em.persist(team);

위의 코드처럼 연관관계의 주인이 아닌 team에만 외래키의 값을 지정하고 저장하는 경우

연관관계의 주인이 아니어서 외래키 값을 변경할 수 없어서 member의 TEAM_ID 값은 null로 나오게 된다.

 

연관관계의 주인에만 값을 입력해도 외래키에 정상 입력값이 들어간다.

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

 

순수한 객체 상태에서도 동작하도록 하기위해서는 연관관계의 주인이 아닌 곳에도 값을 입력해줘야 한다. 하지만 이렇게 하다보면 입력하는 과정에서 실수로 둘 중 하나만 입력해서 양방향 관계가 깨지는 경우도 있을 것이다. 그래서 이를 막기 위해서 연관관계의 주인에서 값을 입력하는 setter를 수정한다.

public class Member {
    private Team team;
    
    public void setTeam(Team team) {
    	this.team = team;
        team.getMembers.add(this);
    }
}

위의 코드에서 문제점이 있다.

만약 기존에 팀이 있었다면 setTeam()을 호출하는 경우 기존의 team은 아직 member를 가리키고 있게된다.

그럼 member는 이미 다른 team으로 이동하였는데 team은 아직 member가 자신의 team에 속하는 것으로 된다.

그래서 이를 막기위해 조건이 추가된다.

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);
    }
}

 

결론은 객체에서 양방향 연관관계를 쓰려면 로직을 견고하게 작성해야 한다는 것이다. 반면에 RDB에서는 외래키 하나로 문제를 해결 할 수 있다. 연관관계의 주인은 외래키의 위치에 중점을 두고 정해야한다. 비즈니스 중요도로 접근하면 안된다.

728x90

'Spring' 카테고리의 다른 글

다양한 연관관계 매핑  (0) 2024.10.05
필드와 컬럼 맵핑  (1) 2024.10.03
엔티티 매핑  (0) 2024.09.29
영속성 관리  (1) 2024.09.22
JPA Application 개발  (0) 2024.09.21
'Spring' 카테고리의 다른 글
  • 다양한 연관관계 매핑
  • 필드와 컬럼 맵핑
  • 엔티티 매핑
  • 영속성 관리
potatoo
potatoo
개발하는 감자
  • potatoo
    감자의 개발일지
    potatoo
  • 전체
    오늘
    어제
    • 분류 전체보기 (198) N
      • 노예 일지 (7)
        • 스타트업 노예일지 (3)
      • CS 이론 (81)
        • 학과 수업 (4)
        • 알고리즘 (64)
        • 시스템 프로그래밍 (3)
        • 데이터 통신 (1)
        • 운영체제 (2)
        • 데이터베이스 (1)
      • project (3)
      • 나는 감자다. (4)
      • Spring (27)
      • 모각코 (45)
        • 절개와지조(모각코) (7)
        • 어쩌다보니 박준태가 조장이조 (11)
        • 어쩌다보니 박준태가 또 조장이조 (12)
      • LikeLion🦁 (20)
      • 캘리포니아 감자 (4)
      • OpenSource Contribute (1)
      • 우아한테크감자 (0)
        • 프리코스 회고록 (6) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    BFS
    Spring
    절개와지조
    8기
    나는 감자
    오블완
    뛰슈
    DFS
    회고록
    자바
    티스토리챌린지
    누적합
    그래프 순회
    감자
    프리코스
    JPA
    백준
    타임리프
    모각코
    어렵다
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
potatoo
연관관계 매핑
상단으로

티스토리툴바