[JPA] 테이블 연관관계 매핑 기초

2023. 2. 10. 17:35·Study/Spring

1. 연관관계 매핑 기초

엔티티들은 대부분 다른 엔티티와 연관관계가 있다. 예를 들어 주문 엔티티는 어떤 상품을 주문했는지 알기 위해 상품 엔티티와 연관관계가 있고 상품 엔티티는 카테고리, 재고 등 또 다른 엔티티와 관계가 있다. 그런데 객체는 참조(주소)를 사용해서 관계를 맺고 테이블은 외래 키를 사용해서 관계를 맺는다. 객체 매칭 관계에서 가장 어려운 부분이 바로 객체 연관관계와 테이블 연관관계를 매핑하는 일이다.

 

방향(Direction) : [단방향, 양방향]이 있다. 예를 들어 회원과 팀이 관계가 있을 때 회원 -> 팀 또는 팀 -> 회원 둘 중 한쪽만 참조하는 것을 단방향 관계라 하고, 회원 -> 팀, 팀 -> 회원 양쪽 모두 서로 참조하는 것을 양방향 관계라 한다. 방향을 객체관계에만 존재하고 테이블 관계는 항상 양방향이다.

 

다중성(Multiplicity) : [다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:N)] 다중성이다. 예를 들어 회원과 팀이 관계가 있을 때 여러 회원은 한 팀에 속하므로 회원과 팀은 다대일 관계다. 반대로 한 팀에 여러 회원이 소속될 수 있으므로 팀과 회원은 일대다 관계다.

 

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


1.1 단방향 연관관계

회원과 팀의 관계를 통해 다대일 관계를 알아보자.

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 다대일 관계다.

그림은 분석해보자.

 

  • 객체 연관관계
    • 회원 객체는 Member.team 필드(멤버변수)로 팀 객체와 연관관계를 맺는다.
    • 회원과 팀 객체는 단방향 관계다. 회원은 Member.team 필드를 통해서 팀을 알 수 있지만 반대로 팀은 회원을 알 수 없다.
  • 테이블 연관관계
    • 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
    • 회원 테이블과 팀 테이블은 양방향 관계다. 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고 반대로 팀과 회원도 조인할 수 있다.
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.ID;

 

SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID;
  • 객체 연관관계와 테이블 연관관계의 가장 큰 차이
    • 참조를 통한 연관관계는 객체 간의 연관관계를 양방향으로 만들고 싶으면 반대쪽에도 필드를 추가해서 참조를 보관해야 한다. 결국 연관관계를 하나 더 만들어야 한다.
    • 이렇게 양쪽에서 서로 참조하는 것을 양방향 연관관계라 한다. 정확히 이야기하면 이것은 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
    • 반면에 테이블은 외래 키 하나로 양방향으로 조인할 수 있다.
    • 양방향 연관관계
class A {
  B b;
}
class B{ }

 

1.1.1 순수한 객체 연관관계

public class Member {
	private String id;
    private String username;
    
    @Getter
    @Setter
    private Team team; //팀의 참조를 보관
}
@Getter
@Setter
public class Team {
	private String id;
    private string name;
}

 

회원 1, 회원 2를 팀 1에 소속시켜 보자

Team team1 = new Team("team1", "팀1");

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

member1.setTeam(team);
member2.setTeam(team);

Team findTeam = member1.getTeam();

 

1.1.2 테이블 연관관계

CREATE TABLE MEMBER (
    MEMBER_ID VARCHAR(255) NOT NULL,
    TEAM_ID VARCHAR(255),
    USERNAME VARCHAR(255),
    PRIMARY KEY (MEMBER_ID)
)

CREATE TABLE TEAM (
    TEAM_ID VARCHAR(255) NOT NULL,
    NAME VARCHAR(255),
    PRIMARY KEY (TEAM_ID)
)

ALTER TABLE MEMBER ADD CONSTRAINT FK_MEMBER_TEAM
	FORIGN KEY (TEAM_ID) REFERENCES TEAM;

 

1.1.3 객체 관계 매핑

 

@Entity
@Getter
@Setter
public class Member {
    @Id
    @Column(name = "MEMBER_ID")
    private String id;
    
    private String username;
    
    //연관관계 매핑
    @ManyToOne
    @Joincolumn(name = "TEAM_ID")
    private Team team;
 }
 
 @Entity
 @Getter
 @Setter
 public class Team {
     @Id
     @Column(name = "TEAM_ID")
     private String id;
     
     private String name;
 }

 

1.1.4 @JoinColumn

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

 

 

1.1.5 @ManyToOne

속성 기능 기본값
optional false로 설정하면 연관된 엔티티가 항상 있어야 함 true
fetch 글로벌 패치 전략을 설정 @ManyToOne=EAGER,
@OneToMany=LAZY
cascade 영속성 전이 -
targetEntity 연관된 엔티티의 타입 정보를 설정(제네릭) -

 

 

1.2 연관관계 사용

1.2.1 저장

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

//회원1 저장
Member member1 = new Member("member1", "회원1");
member1.setTeam(team);
em.persist(member1);

 

 

1.2.2 조회

Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); //객체 그래프 탐색

 

 

1.2.3 수정

//새로운 팀2
Team team2 = new Team("team2", "팀2");
em.persist(team2);

//회원 1에 새로운 팀2 설정
Member member1 = em.find(Member.class, "member1");
member1.setTeam(team2);
  • 단순히 불러온 엔티티의 값만 변경해 두면 트랜잭션을 커밋할 때 플러시가 일어나면서 변경 감지 기능이 작동
  • 변경 사항을 DB에 자동으로 반영

 

1.2.4 연관관계 제거

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

 

1.2.5 연관된 엔티티 삭제

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

 

 

 

1.3 양방향 연관관계

 

  • 양방향 매핑의 장점
    • 단방향 매핑으로 이미 연관관계 매핑은 완료
    • 양방향은 반대 방향으로 객체 그래프 탐색 기능이 추가된 것
    • 단방향 매핑을 잘하고 양방향 매핑은 필요할 때 추가해도 됨

 

1.3.1 양방향 연관관계 매핑

@Entity
public class Member {
    //동일
}

@Entity
public class Team {
  //추가되는 부분
  @OneToMany(mappedBy = "team")
  private List<Member> members = new ArrayList<>();
}
  • mappedBy
    • 양방향 매핑일 때 사용
    • 반대쪽 매핑의 필드 이름을 값으로 사용
    • 반대쪽 매핑이 Member.team 이므로 "team"

 

1.3.2 일대다 컬렉션 조회

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

1.4 연관관계의 주인

  • 객체에는 양방향 연관관계는 없음
    • 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 양방향인 것처럼 보이게 할 뿐
  • 데이터베이스 테이블은 외래 키 하나로 양쪽이 서로 조인 가능
    • 테이블은 외래 키 하나만으로 양방향 연관관계 가능
  • 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래키는 하나
    • 따라서 둘 사이에 차이가 발생
    • 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리해야 하는데 이것을 연관관계의 주인(Owner)이라 함

 

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

  • 두 연관관계 중 하나를 연관관계의 주인으로 설정
  • 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있음
  • 반면에 주인이 아닌 쪽은 읽기만 할 수 있음
  • 주인은 mappedBy 속성을 사용하지 않음
  • 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것

 

 

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

  • 회원 테이블이 외래 키를 가지고 있으므로 Member.team이 주인
  • 주인이 아닌 Team.members에는 mappedBy="team" 속성을 사용해서 주인이 아님을 설정
    • 여기서 mappedBy값으로 사용된 team은 연관관계의 주인인 Member 엔티티의 team 필드를 말함
데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래키를 가짐
다 쪽인 @ManyToOne은 항상 연관관계의 주인이 되므로 mappedBy를 설정할 수 없다.

 

1.5 양방향 연관관계 저장

5.2.1 참조
member1.setTeam(team); //연관관계 설정(연관관계의 주인)

 

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

//회원1 저장
Member member1 = new Member("member1", "회원1");
em.persist(member1);

Team team = new Team("team1", "팀1");
//주인이 아닌 곳만 연관관계 설정
team.getMembers().add(member1);
em.persist(team1);

 

 

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

  • 객체 관점에서 양쪽 방향에 모두 값을 입력해 주는 것이 가장 안전
    • 양쪽 방향에 모두 값을 입력하지 않으면 순수한 객체 상태에서 심각한 문제가 발생할 수 있음
//팀1
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
member1.setTeam(team); //연관관계 설정 member1 -> team1

List<Member> members = team1.getMembers();
//members.size()는?
  • ORM은 객체와 데이터베이스 둘 다 고려
//팀1
Team team1 = new Team("team1", "팀1");
em.persist(team1);

Member member1 = new Member("member1", "회원1");
//양방향 연관관계 설정
member1.setTeam(team);				//연관관계 설정 member1 -> team1
team1.getMembers().add(member1);	//연관관계 설정 team1 -> member1, 저장시 사용하지는 않음
em.persist(member1);

 

 

1.6.2 연관관계 편의 메소드

  • 양방향 연관관계는 결국 양쪽 다 신경을 써야 함
    • 실수로 둘 중 하나만 호출해서 양방향이 깨질 수 있음
public class Member {
    private Team team;
    
    public void setTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}


//연관관계 설정
member1.setTeam(team1);

 

 

 

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

member1.setTeam(teamA);
member1.setTeam(teamB);
Member findMember = teamA.getMember(); //member1이 여전히 조회
  • teamB로 변경할 때 teamA -> member1 관계를 제거하지 않음
public class Member {
    public void setTeam(Team team) {
    //기존 팀과 관계를 제거
    if (this.team != null) {
        this.team.getMembers().remove(this);
    }
    this.team = team;
    team.getMembers().add(this);
  }
}

'Study > Spring' 카테고리의 다른 글

[Spring 핵심 원리 - 기본편] 웹 애플리케이션과 싱글톤  (0) 2023.02.19
[JPA] QueryDSL 이란  (0) 2023.02.17
[Spring] Spring Security란?  (0) 2023.01.31
[Spring] 좋은 객체 지향 프로그래밍이란?  (0) 2023.01.24
[Spring] JPA 란?  (0) 2023.01.15
'Study/Spring' 카테고리의 다른 글
  • [Spring 핵심 원리 - 기본편] 웹 애플리케이션과 싱글톤
  • [JPA] QueryDSL 이란
  • [Spring] Spring Security란?
  • [Spring] 좋은 객체 지향 프로그래밍이란?
개발새발개발
개발새발개발
  • 개발새발개발
    끄저억끄저억
    개발새발개발
  • 전체
    오늘
    어제
    • 분류 전체보기 (57)
      • Study (45)
        • DB (9)
        • WEB (11)
        • Spring (14)
        • JS (5)
        • Python (2)
        • IntelliJ (4)
      • 이슈 해결 (2)
      • Challenge (4)
        • 구름톤 챌린지 (3)
        • 자격증 (0)
      • 우아한테크코스 (2)
      • Dev Camp 3기 (0)
      • 개발 Tip (3)
      • 일상 (1)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    Java
    객체지향
    NULL
    Spring
    realforce r3
    IntelliJ
    db
    김영한
    우테코
    레디스
    web
    싱글톤
    우아한테크코스
    jwt
    JPA
    Redis
    til
    DBMS
    singleton
    스프링
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
개발새발개발
[JPA] 테이블 연관관계 매핑 기초
상단으로

티스토리툴바