김영한 님의 "자바 ORM 표준 JPA 프로그래밍" 책을 정리한 포스팅 입니다.
1. 연관 관계 매핑
구분 | 설명 | 방향성 | 비고 |
객체 연관 관계 | 참조를 통해 연관관계 표현 (참조에 의해 객체 그래프 탐색) |
단방향 A → B (B는 A를 모름) |
객체 그래프 탐색 제한 |
테이블 연관 관계 | 외래키를 통해 연관관계 표현 (조인을 통해 연관 테이블 조회) |
양방향 A ↔ B (서로 참조) |
무한 루프 주의 |
어노테이션
항목 | 설명 | 기본값 |
@JoinColumn | 외래키 설정 (조인 시 사용) | - |
- name | 외래키 컬럼 이름 | - |
- referencedColumnName | 외래키가 참조하는 컬럼명 (기본값: 참조하는 엔티티의 기본 키) | - |
- foreignKey | 외래키 제약조건 (테이블 생성 시에만 적용됨) | - |
@ManyToOne | 다대일 관계 설정 | - |
- optional | 관련 엔티티가 존재하는지 여부 (기본: true) | TRUE |
- fetch | 페치 전략 설정 | FetchType.EAGER |
- cascade | 영속성 전이 기능 (연관된 엔티티도 영속성 전이가 가능) | CascadeType.ALL |
- targetEntity | 관련 엔티티 타입 지정 | - |
@OneToMany | 일대다 관계 설정 | |
- mappedBy | 양방향 매핑에서 연관관계 주인 설정 (반대편 매핑 객체의 필드 이름 지정) |
예시) @JoinColumn
더보기
@Entity
public class User {
@ManyToOne
@JoinColumn(name = "team_id", referencedColumnName = "team_code")
private Team team;
}
// name: 외래키 컬럼 이름을 "team_id"로 설정
// referencedColumnName: 외래키가 슈퍼타입 테이블의 "team_code"를 참조
@Entity
public class User {
@ManyToOne
@JoinColumn(name = "team_id", foreignKey = @ForeignKey(name = "FK_USER_TEAM"))
private Team team;
}
// foreignKey: 외래키 제약조건을 명시적으로 이름을 지정
예시) @ManyToOne
더보기
@Entity
public class User {
@ManyToOne(optional = false) // Team이 반드시 존재해야 한다.
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class User {
@ManyToOne(fetch = FetchType.LAZY) // team 정보를 지연 로딩
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class User {
@ManyToOne(fetch = FetchType.EAGER) // team 정보를 즉시 로딩
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class User {
@ManyToOne(cascade = CascadeType.PERSIST) // User 저장 시 연관된 Team도 저장
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class User {
@ManyToOne(targetEntity = Team.class) // 연관된 엔티티 타입을 명시적으로 지정
@JoinColumn(name = "team_id")
private Team team;
}
예제) @OneToMany
더보기
@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")
private List<member> members = new ArrayList<Member>();
}
2. 양방향 연관관계
연관관계 주인
- 테이블 기준으로, 외래키 하나로 두 테이블의 연관관계 관리
- 객체 기준으로, 양방향 연관관계 존재 X
- 테이블과 마찬가지로 객체에서도 양방향 조회가 가능하도록 함
- 단방향 연관관계 2개를 묶어 구현 (두 개의 참조 발생)
- 외래키를 관리할 주인 설정 필요 (연관관계 주인)
구분 | 설명 |
주인 (Owning Side) |
외래키 지정 및 관리 (설정, 변경)
|
주인이 아닌 쪽 |
읽기만 가능
|
예제
더보기
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "team_id") // 🔸 연관관계의 주인 (외래키)
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this); // 편의 메서드 (비주인도 동기화)
}
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // 🔹 연관관계의 비주인 (mappedBy 설정)
private List<Member> members = new ArrayList<>();
}
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("noah");
// 연관관계 주인에만 값 설정해야 외래키 제대로 들어감
member.setTeam(team); // 🔥 여기 설정이 핵심
em.persist(member);
주의점
문제 유형 | 원인 | 해결 방안 |
연관관계 누락 시 DB에 null 저장 | 연관관계의 주인 객체에 값을 설정하지 않음 | 주인 객체에 반드시 연관관계 설정 |
연관관계 변경 시 이전 객체에 정보가 남음 | 연관관계 변경 시, 기존 연관 객체에서 제거 안함 | 기존 객체에서 remove(this) 수행 |
무한 루프 (순환 참조) | toString(), equals(), hashCode(), JSON 직렬화 시 서로 참조 |
1. toString()에서 연관 필드 제외
2. @JsonManagedReference / @JsonBackReference 사용 |
예시) 연관관계 누락 시 DB에 null 저장
더보기
문제
Member member = new Member(); // 주인
member.setName("Member1");
em.persist(member);
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member);
em.persist(team);
해결방안 1 - 주인 객체에 반드시 연관관계 설정
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member(); // 주인
member.setName("Member1");
member.setTeam(team);
em.persist(member);
team.getMembers().add(member);
해결방안 2 - 연관관계 편의 메서드
public class Member {
// ...
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
예시) 연관관계 변경 시 이전 객체에 정보가 남음
더보기
문제
member1.setTeam(team1);
member1.setTeam(team2);
Member findMember = team1.getMembers().get(0); // 여전히 member1이 조회된다.
해결 방안 - 기존 객체의 연관관계 삭제하기
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
예시) 무한 루프 (순환 참조)
더보기
해결 방안 - toString() 오버라이딩
public class Team {
private String name;
private List<Member> members = new ArrayList<>();
@Override
public String toString() {
return "Team{name='" + name + "'}"; // members 리스트 제외
}
}
public class Member {
private String name;
private Team team;
@Override
public String toString() {
return "Member{name='" + name + "'}"; // team 필드 제외
}
}
해결 방안 2 - JSON Serializing
public class Team {
private String name;
@JsonManagedReference
private List<Member> members = new ArrayList<>();
}
public class Member {
private String name;
@JsonBackReference
private Team team;
}
- @JsonManagedReference: 직렬화 시, 포함되는 필드 지정
- @JsonBackReference: 직렬화 시, 제외되는 필드 지정
3. 연관관계 연산
작업 | 설명 | 구체적인 방법 |
저장 | 참조 객체와 함께 DB에 저장 |
1. 슈퍼타입: 참조 객체 저장
2. 서브타입: 객체 저장 |
조회 | 연관관계를 사용하여 조회 | 1. 객체 그래프 탐색 () 2. JPQL 사용 |
수정 | 커밋할 때 변경 감지 기능이 동작 |
1. 엔티티가 변경되면 변경 감지
2. flush() 호출 시, 변경된 데이터를 DB에 반영 |
제거 | 연관관계를 사용하여 삭제 |
연관된 엔티티를 null로 설정하여 연관 관계 해제 후, 저장하면 삭제됨
|
예제) 조회
더보기
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();
예제) 제거
더보기
private void deleteRelation(EntityManager em) {
Member member = em.find(Member.class, "member1");
member.setTeam(null);
}
'Spring > Spring Data JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 7. 고급 매핑 (2) | 2023.12.28 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 6. 다양한 연관관계 매핑 (0) | 2023.12.27 |
[자바 ORM 표준 JPA 프로그래밍] 4. 엔티티 매핑 (1) | 2023.11.28 |
[자바 ORM 표준 JPA 프로그래밍] 3. 영속성 관리 (0) | 2023.11.28 |
[자바 ORM 표준 JPA 프로그래밍] 2. JPA 시작 (0) | 2023.11.28 |