단방향: 한 쪽 엔티티에서만 다른 엔티티를 참조하는 관계. @JoinColumn을 사용하여 외래 키를 관리하는 엔티티에서만 연관 관계를 설정한다.
양방향: 양쪽 엔티티 모두 서로를 참조하는 관계. 한 쪽은 @JoinColumn으로 외래 키를 관리하고, 반대쪽은 mappedBy 속성을 사용하여 연관 관계의 주인이 아님을 명시 → 자주 접근하는 테이블을 주 테이블로 설정할 것 ❗️
toString()
, JSON 직렬화에서 순환 참조가 발생 가능성이 있다.
순환 참조 발생 예시
parent.toString()
실행 → children.toString()
실행child.toString()
실행 → parent.toString()
다시 실행StackOverflowError
발생→ 해결책: DTO(Data Transfer Object) 변환 후 JSON 직렬화, Lombok의 @ToString.Exclude
사용
팀 엔티티에서만 멤버 엔티티들을 컬렉션으로 참조하는 관계. 팀 테이블(일)에는 멤버 테이블(다)을 참조하는 외래 키가 없다. 대신, 멤버 테이블에 팀 테이블을 참조하는 외래 키가 존재한다.
특징: 팀 엔티티가 연관관계의 주인이 되며, @JoinColumn을 팀 엔티티에 작성한다. 하지만, 실제 외래 키는 멤버 테이블에 생성(DB상에서). 이는 JPA 구현 방식의 특징이며, 성능 이슈의 주 원인이 된다.
문제점:
UPDATE
쿼리를 실행할 수 있다.-- (1) 팀 저장
INSERT INTO Team (name) VALUES ('Team A'); -- ✅ 정상 동작
-- (2) 멤버 저장 (이때, team_id는 NULL)
INSERT INTO Member (name) VALUES ('Member 1'); -- ❌ team_id 없음
INSERT INTO Member (name) VALUES ('Member 2'); -- ❌ team_id 없음
-- (3) 팀과 멤버 연결을 위한 UPDATE 추가 실행
UPDATE Member SET team_id = 1 WHERE id = 1; -- ⚠ 추가적인 UPDATE 발생
UPDATE Member SET team_id = 1 WHERE id = 2; -- ⚠ 추가적인 UPDATE 발생
왜 UPDATE
쿼리가 발생할까?
@OneToMany
는 연관 관계의 주인이 아님!@OneToMany
관계에서 연관 관계의 주인이 Team
이라고 착각함.Member
테이블에 team_id
를 업데이트하는 추가적인 UPDATE
쿼리를 실행함.즉, INSERT만으로 해결되지 않고, UPDATE가 한 번 더 발생하는 구조가 됨.
→ 양방향 관계(@OneToMany
+ @ManyToOne
)를 사용하면 UPDATE
쿼리를 제거할 수 있음!
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // (1) 연관관계의 주인이 아님 (읽기 전용)
private List<Member> members = new ArrayList<>();
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "team_id") // (2) 외래 키를 직접 관리하는 주인!
private Team team;
}
👉 추가적인 UPDATE
쿼리를 실행 해결
-- (1) 팀 저장
INSERT INTO Team (name) VALUES ('Team A'); -- ✅ 정상 동작
-- (2) 멤버 저장 (team_id를 함께 저장!)
INSERT INTO Member (name, team_id) VALUES ('Member 1', 1); -- ✅ UPDATE 필요 없음!
INSERT INTO Member (name, team_id) VALUES ('Member 2', 1); -- ✅ UPDATE 필요 없음!
INSERT 시에 team_id가 바로 저장됨. JPA가 불필요한 추가 UPDATE를 실행하지 않음.
new ArrayList<>()
). → NullPointerExceptionSet
타입을 고려