단방향 연관관계
회원테이블과 팀 테이블이 있다.
회원은 하나의 팀에만 속할 수 있고, 1개의 팀에는 여러 명의 회원이 있다.
Member : Team = 다 : 1 관계이다.
Member가 다고,
Team이 1이다.
객체 연관관계
회원 객체, 팀 객체는 단방향 관계다.
Member.team : Member 객체에서 Team객체는 참조 가능하다.
Team.member : Team객체에서 Member객체는 참조할 수 없다.
테이블 연관관계
회원 테이블, 팀 테이블은 양방향 관계다.
회원 테이블의 TEAM_ID로 팀 테이블 조인 가능하다.
MEMBER JOIN TEAM, TEAM JOIN MEMBER 가 가능하므로 양방향 관계이다.
객체 연관관계와 테이블 연관관계의 가장 큰 차이
객체는 클래스에 다른 클래스 필드를 참조해야하기때문에 양방향으로 만들고 싶으면,
서로 다른 단방향 관계 2개를 만들어야 한다.
순수한 객체 연관관계
객체 그래프 탐색: 객체를 참조해서 연관관계를 탐색한다.
Team findTeam = member1.getTeam();
테이블 연관관계
외래키와 조인을 사용해서 연관관계를 탐색할 수 있다.
객체 관계 매핑
객체와 테이블의 서로 다른 연관관계를 매핑할 수 있다.
@Entity
public class Member{
@Id
@Column(name="MEMBER_ID")
private String id;
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
}
@Entity
public class Team{
@Id
@Column(name="TEAM_ID")
private String id;
}
@JoinColumn
외래키를 매핑할 때 사용한다.
@ManyToOne
다대일 관계에서 사용한다.
연관관계 사용
저장
member1.setTeam(team1);
em.persist(member1);
조회
1. 객체 그래프 탐색
객체를 통해 연관된 엔티티를 조회하는 것
Member member = em.find(Member.class, "member1");
Team team = member.getTeam();
2. 객체지향 쿼리 사용
// JPQL을 사용한 예시
SELECT m
FROM MEMBER m JOIN m.team t
WHERE t.name = teamName
수정
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
불러온 엔티티 값만 수정해두면 변경 감지 기능이 동작하여 자동으로 데이터베이스에 UPDATE 쿼리문이 날아간다.
따라서 em.update() 같은 메소드를 쓸 필요가 없다.
연관관계 제거
member.setTeam(null);
연관된 엔티티 삭제
연관된 엔티티를 삭제하려면 연관관계를 제거하고 삭제해야 외래 키 제약조건이 발생하지 않는다.
member.setTeam(null);
em.remove(team);
양방향 연관관계
@Entity
public class Member{
@Id
@Column(name="MEMBER_ID")
private String id;
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
}
@Entity
public class Team{
@Id
@Column(name="TEAM_ID")
private String id;
// 추가되는 부분
@OneToMany(mappedBy = "team") // Member class 에서 사용되는 매핑 필드 값
private List<Member> members = new ArrayList<Member>();
}
일대다 컬렉션 조회
Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers();
연관관계 주인
엔티티를 양방향 관계로 설정하면 객체의 참조는 2개, 외래키는 1개다.
이로 인해 생기는 문제점이 있다.
1. 일관성 유지 어려움
객체 참조가 2개이기 때문에 양쪽에서 한 쪽 엔티티를 수정할 때 다른 쪽 엔티티의 값도 변경해줘야 한다.
2. 무한루프
@ToString같은 어노테이션의 경우 양쪽 객체에 붙어있으면 무한루프가 발생한다.
3. 성능 문제
데이터베이스 쿼리를 작성할 때 조인을 사용하게 되면 사용하지 않는 불필요한 데이터까지 가져오게 된다.
이때 성능 문제가 생길 수 있다.
따라서 어떤 객체의 참조가 외래키를 관리할지 정해야 하는데 이를 연관관계의 주인이라고 한다.
객체의 참조
회원 -> 팀
팀 -> 회원
외래키
TEAM_ID
양방향 매핑의 규칙: 연관관계의 주인
연관관계 주인은 외래 키를 등록, 수정, 삭제할 수 있다.
주인이 아닌 클래스는 mappedBy 속성을 사용해서 주인을 지정해야 한다.
연관관계의 주인은 외래 키가 있는 곳
연관관계 주인은 외래키가 있는 테이블을 관리하는 클래스로 정해야 한다.
항상 @ManyToOne 쪽이 연관관계의 주인이 되므로 mappedBy 속성이 없다.
@ManyToOne 관계에서 연관관계 주인이 One인 경우 발생하는 문제점 (회원 - 팀 관계에서 연관관계 주인이 팀일 때 발생하는 문제점)
1. Cascade 문제
연관관계 주인인 팀을 삭제할 경우 원치 않은 회원들까지 모두 삭제될 수 있어서 주의가 필요하다.
2. 데이터베이스 성능 최적화 문제
팀을 조회하면서 항상 모든 회원들이 조회될 경우, 회원 수가 많으면 성능 문제가 발생할 수 있다.
양방향 연관관계 저장
// 연관관계의 주인이 아니기 때문에 무시된다.
team1.getMembers().add(member1);
// 연관관계가 설정된다.
member1.setTeam(Team1);
양방향 연관관계의 주의점
순수한 객체까지 고려한 양방향 연관관계
객체 관점에서는 양쪽 방향에 모두 값을 입력해주는 것이 안전하다.
그래야 애플리케이션 레벨에서 team1에서도 member 목록을 가져올 수 있고, member1에서도 team 정보를 가져올 수 있다.
public void Relation () {
//팀1 저장
Team team1 = new Team();
em.persist(team1);
Member member1 = new Member();
//양방향 연관관계 설정
member1.setTeam(team1) ; //연관관계 설정 member 1 -> team1
team1.getMembers().add(member1); //연관관계 설정 team1 -> member 1
em.persist(member1);
}
연관관계 편의 메소드
양방향 연관관계를 고려해서 set을 양쪽 다 하다 보면 실수할 수 있다.
따라서 1개의 set()메소드에서 양방향 연관관계를 설정하는 것이 좋다.
@Entity
public class Member{
...
public void setTeam(Team team) {
//양방향 연관관계 설정
this.setTeam(team) ;
team.getMembers().add(this);
}
...
}
연관관계 편의 메소드 작성 시 주의사항
member1.setTeam(teamA);
member1.setTeam(teamB);
위와 같은 코드처럼 member1을 teamA 로 세팅하면 member1 -> teamA , teamA -> member1 와 같이 양방향 관계가 생긴다.
그리고 member1을 teamB로 세팅하면 member1 -> teamB , teamB -> member1 관계가 생긴다. 이 때 teamA -> member1 관계는 삭제되지 않는다.
따라서 기존 연관관계를 다른 연관관계로 교체할 때 기존 연관관계를 깨끗하게 없애야 한다.
@Entity
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);
}
}
정리
- 단방향 매핑만으로 연관관계 매핑은 완료된다.
- 양뱡향 연관관계를 사용할 때는 양쪽 객체에 필드 값을 입력해야 하고, 다른 연관관계로 교체할 때 기존 연관관계를 깨끗하게 없애야 하므로 필요할 때만 양방향 연관관계를 사용해야 한다.
- 연관관계 주인은 외래 키가 있는 곳으로 정해야 한다.
- 비즈니스 중요도로 접근하면 안 된다. 차 클래스, 바퀴 클래스가 있을 때 비즈니스적으로는 차가 더 중요하지만 객체적으로는 바퀴가 차 클래스의 외래 키를 가지고 있기 때문에 바퀴가 연관관계의 주인이 된다.
- 양방향 매핑에서 @ToString 무한루프에 빠지지 않도록 주의해야 한다.
5장 실습예제 코드는 https://github.com/mjkim103301/jpa-practice 여기를 참고하면 됩니다.
참고자료
- https://www.yes24.com/Product/Goods/19040233 "자바 ORM 표준 JPA 프로그래밍-김영한"
'Coding > computer science' 카테고리의 다른 글
자바 ORM 표준 JPA 프로그래밍 | 07. 고급 매핑 (0) | 2023.10.05 |
---|---|
자바 ORM 표준 JPA 프로그래밍 | 06. 다양한 연관관계 매핑 (0) | 2023.09.18 |
자바 ORM 표준 JPA 프로그래밍 | 04. 엔티티 매핑 (0) | 2023.09.06 |
자바 ORM 표준 JPA 프로그래밍 | 03. 영속성 관리 (0) | 2023.08.29 |
자바 ORM 표준 JPA 프로그래밍 | 02. JPA 시작 (0) | 2023.08.22 |