테이블 간의 연관관계
기존 테이블 간의 연관관계 설정
기본적으로 데이터베이스에서 엔티티(테이블) 간의 연관관계는 Foreign Key를 통해서 하는 것을 알고 있을 것이다.
그러나 Java에서 이 Key를 가지고 다른 테이블을 넘나들려면 꽤나 긴 과정이 필요하다.
Member 엔티티와 멤버가 소속된 Team 엔티티를 가정해 보자.
[JPA 사용 시 가능해지는 연관관계 설정 예제]
예제(memberId로 멤버가 소속된 팀의 이름을 찾아보자)
Member객체는 필드로 Long teamId를 갖고 데이터베이스 MEMBER 테이블이 TEAM_ID 럼을 갖게 될 것이다.
이런 경우 memberA라는 멤버의 team의 이름을 구하려고 하면 어떻게 해야 할까?
Member member = memberRepository.findById(id);
Long teamId = member.getTeamId();
Team team = teamRepository.findById(teamId);
String teamName = team.getName(); //구하고자 하는 정보
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
그리고 위의 코드는 객체지향과는 거리가 있어 보인다.
이런 귀찮은 과정을 JPA는 좀 더 쉽게 해결해 준다.
위의 예제 상황에서 JPA를 사용한다면?)
JPA를 사용하면 Member객체의 필드로 Team 속성을 가질 수 있고, 이를 데이터베이스와 적절히 매핑해 준다.
그렇다면 아래와 같은 코드가 가능해질 것으로 예상할 수 있다.
Member member = memberRepository.findById(id);
String teamName = member.getTeam().getName(); // 구하고자 하는 정보
Member엔티티가 Team을 필드로 가질 수 있게 되니 훨씬 코드가 간결해진 것을 확인할 수 있다.
JPA는 데이터베이스 테이블의 규칙을 거스르지 않으면서 Java에서는 객체지향적으로 엔티티를 관리할 수 있게 도와준다.
JPA의 연관관계 설정
단방향 연관관계
JPA는 객체의 참조와 테이블의 외래키를 매핑하여 객체 지향적으로 엔티티를 관리할 수 있다.
간단한 예제를 통해 알아보자.
아래는 Team 엔티티 클래스이다.
Team 클래스에는 Member에 대한 정보가 없다.
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
}
아래는 Member 엔티티 클래스이다.
Member 클래스는 Team team을 필드로 가진다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
기존에는 Team team 필드 대신에 Long teamId가 들어갈 것이다.
하지만 JPA를 이용해서 몇 가지 annotation과 함께 Team 필드를 이용해서 연관관계를 설정할 수 있다.
ManyToOne, OneToMany?
@xxxToXxx에서
앞쪽 xxx는 항상 해당 필드가 위치한 클래스 자신(Member)이 되고,
뒤쪽 xxx는 항상 매핑할 대상 클래스(Team)가 된다.
데이터베이스 테이블은?
@ManyToOne에서 Member이 Many이고 Team이 One이다.
여러 명의 멤버가 하나의 팀에 소속되어 있을 테니 당연한 것이다.
그리고, Foreign Key는 Many 쪽에 위치하는 게 일반적일 것이다.
위의 그림과 같이 JPA로 연관 관계를 매핑하면 JPA가 자동으로 쿼리를 생성하여
데이터베이스에는 @JoinColumn으로 지정된 name 속성의 값으로 MEMBER 테이블의 럼 명이 설정된다.
양방향 연관관계
위의 예제에서 Member는 Team에 소속되어 있고, Team은 자신(Team)에 소속된 멤버들이 존재한다.
여기서 Member가 Team에 소속되어 있는 것은 MEMBER 테이블의 FK로 TEAM_ID가 존재하는 것으로 확인할 수 있다.
그런데 Team이 자신(Team)에 속해있는 멤버 정보가 필요할 수도 있지 않겠는가?
이러한 상황을 위해 JPA는 양방향 연관관계를 지원한다.
우선 양방향 연관관계는 DB에 아무런 영향을 미치지 않는다.
오직 뒤에서 설명할 '연관관계의 주인'만이 DB에 영향을 미친다.
즉 단방향 연관관계가 기본이고, 양방향 연관관계는 부가적인 것이라고 생각하면 좋을 것 같다.
Member 엔티티는 위와 동일하다.
//class Member
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
Team 엔티티는 추가로 자신에 소속된 멤버 리스트를 필드로 둘 수 있다.
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
위에서 @OneToMany 어노테이션이 추가된 것을 볼 수 있다.
이렇게 Team 클래스에서 members라는 ArrayList 필드를 만들어서 팀에 속한 멤버의 List를 조회할 수 있다.
이 필드는 Team 클래스에 속해있기 때문에
위에서 필드가 속한 클래스가 앞쪽(One)에 오고, 대상 클래스가 뒤쪽(Member)에 온다고 한 것을 기억하자.
양방향 연관관계와 주의 사항
연관 관계의 주인
양방향 연관관계에서는 '연관 관계의 주인'이라는 것이 존재한다.
이 연관 관계의 주인 만이 실제로 데이터를 CRUD 할 수 있고, 반대쪽 대상 클래스는 조회만 가능하다.
Member 클래스의 Team 필드를 맨 처음에 아래와 같이 Team과 매핑했다.
//Member 엔티티 클래스
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
또한, Team 클래스에 멤버 목록을 위한 List 필드를 만들었다.
//Team 엔티티 클래스
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
그러나 데이터베이스에 컬렉션을 담은 컬럼은 존재할 수 없다.
양방향 연관관계를 설정해도 데이터베이스에는 적용되지 않는 것이다.
데이터 베이스는 오직 '연관관계의 주인'의 필드와 어노테이션(@ManyToOne, @JoinColumn)을 따른다.
@JoinColumn(name = "TEAM_ID) 어노테이션에 의해 Member 클래스의 team 필드는 DB의 TEAM_ID(FK)로 매핑되었다. 이렇게 JoinColumn을 설정하고, 실제 DB에 ForeignKey를 가진쪽이 무조건 연관관계의 주인이라고 생각하면 된다.
mappedBy
mappedBy는 연관관계의 주인이 아닌 반대편 클래스에서 매핑된 대상을 명시하도록 설정할 수 있는 속성이다.
일반적으로 @ManyToOne, @OneToMany, @OneToOne, @ManyToMany 어노테이션이 있으면 해당 클래스를 연관관계의 주인으로 하여 매핑한다.
그러나 양방향 연관관계에서 연관관계의 주인이 아닌 반대쪽에 해당 어노테이션이 있으면 필수로 mappedBy 속성과 함께해야 한다.
//Member 엔티티 클래스
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
//Team 엔티티 클래스
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
Team Entity 클래스에서의 @OneToMany(mappedBy = "xxx")는 xxx에 의해 매핑되었다는 뜻이다.
즉, 위의 예제에서 members 필드는 Member 클래스의 "team" 필드와 매핑되었다는 것이다.
이렇게 mappedBy 속성이 붙으면 이것은 연관관계의 주인이 아니라 매핑된 대상을 명시하는 것이 된다.
물론 Team이 One이고 Member가 Many니까 @OneToMany 어노테이션이 붙는다.
양방향 매핑 주의사항 정리
- '연관관계의 주인'과 mappedBy 속성을 꼭 기억하자.
- 실제 DB에 FK를 갖게 되는 쪽을 연관관계의 주인으로 설정하자. ( 이 글에서는 항상 Member가 연관관계의 주인)
- 단방향 매핑만으로 이미 연관관계 매핑은 완료된 것이다.
- 꼭 필요할 때만 양방향 연관관계를 추가하자.
- 연관관계의 주인이 아닌 쪽은 조회만 가능하다.
- 양방향 매핑시에 무한루프를 조심하자. (toString(), lombok, JSON 생성 라이브러리 등)
양방향 매핑 시 무한루프
Member와 Team이 위의 예제 그대로의 양방향 매핑 상태라고 가정하자.
그리고 Team과 Member에 toString이 오버라이딩 되어있다고 가정하자.
System.out.println(memberA.getTeam()); 코드를 실행하면 어떻게 될까?
Team findTeam1 = memberA.getTeam();
findTeam1.toString();
findTeam1에는 Long id, String name, List <Member> members 필드가 있다.
이것을 toString() 하면 id, name은 그냥 출력되고,
members는 리스트를 순환하며 참조된 member를 모두 꺼내서 toString() 할 것이다.
그렇게 되면 members에서 꺼낸 member에는 또 Team 필드가 있고,
그 Team 필드를 다시 toString()하면 다시 그 Team.members 필드가 있을 것이고,
해당 members 리스트에 속한 member들을 순회하며 다시 해당 member들의 team을 참조한다.
이와 같이 무한 루프에 빠지게 된다.
따라서 toString(), lombok, JSON 생성 라이브러리 같은 메서드들을 사용할 때 주의하자.
각 매핑 상세
@ManyToOne, @OneToMany
[Spring/JPA] - [JPA] 연관관계 매핑 - @ManyToOne, @OneToMany
@OneToOne
[Spring/JPA] - [JPA] 연관관계 매핑 - @OneToOne 일대일 매핑
@ManyToMany
[Spring/JPA] - [JPA] 연관 관계 매핑 - @ManyToMany
(참고) 인프런 - 김영한 님 '자바 ORM 표준 JPA 프로그래밍 - 기본 편'
https://www.inflearn.com/course/ORM-JPA-Basic
'Database > JPA' 카테고리의 다른 글
[JPA / Spring] JPQL을 이용해서 Entity가 아닌 DTO를 반환하는 방법 (0) | 2024.05.07 |
---|---|
[JPA] 연관 관계 매핑 - @ManyToMany (0) | 2023.10.06 |
[JPA] 연관관계 매핑 - @OneToOne 일대일 매핑 (0) | 2023.10.06 |
[JPA] 연관관계 매핑 - @ManyToOne, @OneToMany (0) | 2023.10.06 |
[JPA] @Entity - 필드와 컬럼 매핑의 여러가지 속성 (1) | 2023.10.04 |