0730 (JPA N+1 문제, Fetch Join, Cascade, 다대다 연관관계, SQL 서브쿼리)
1. JPA 양방향 연관관계에서의 편의 메서드
changeDepartment: Employee에서 부서를 바꾸는 편의 메서드로, 반대편 관계인 Department.employees에도 추가.
addEmployee: Department에서 사원을 추가할 때 Employee의 setDepartment(this)까지 같이 수행하여 양방향 일관성을 유지.
public void changeDepartment(Department department) {
this.department = department;
if (department != null) {
department.getEmployees().add(this);
}
}
public void addEmployee(Employee employee) {
employee.setDepartment(this);
this.employees.add(employee);
}
2. Fetch 전략과 N+1 문제
N+1 문제란?
- 1개의 쿼리(N)를 실행하고 각 결과마다 추가 쿼리(1)를 반복 실행하는 비효율적 상황.
해결 방법
@OneToMany(fetch = FetchType.LAZY) 설정을 통해 지연 로딩하거나, 명시적으로 Fetch Join 사용.
@Query("SELECT d FROM Department d JOIN FETCH d.employees")
List<Department> findAllByFetch();
3. Cascade와 orphanRemoval
cascade = CascadeType.ALL: 연관 엔티티의 저장, 삭제 등의 작업이 자동 전파됨.
orphanRemoval = true: 관계에서 제거된 엔티티는 DB에서도 자동 삭제됨.
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees;
4. ManyToMany 관계 해소: 중간 엔티티
중간 엔티티 Purchase
User 와 Goods 는 직접 다대다 연결하지 않고 중간 테이블 Purchase 를 사용해 1\:N, N:1 구조로 해소.
@Entity
public class Purchase {
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Goods goods;
}
5. Cascade 활용 예시
- 유저 삭제 시 구매 기록도 같이 삭제됨 (
CascadeType.ALL, orphanRemoval = true 적용)
userRepository.delete(user);
6. SQL 서브쿼리 정리
인라인 뷰 (FROM절 서브쿼리)
SELECT U.USER_ID, U.USERNAME, PC.POST_COUNT
FROM (
SELECT USER_ID, COUNT(*) AS POST_COUNT
FROM POSTS
GROUP BY USER_ID
) PC
JOIN USERS U ON PC.USER_ID = U.USER_ID;
스칼라 서브쿼리 (SELECT절 내부에서 1개의 결과를 가져옴)
SELECT U.USER_ID, U.USERNAME,
(SELECT BIO FROM USER_PROFILES UP WHERE U.USER_ID = UP.USER_ID) AS BIO
FROM USERS U;
연관 서브쿼리 예시
- 게시글 좋아요 수, 댓글 수를 한 번에 가져오기
SELECT
P.POST_ID,
P.CONTENT,
(SELECT COUNT(*) FROM LIKES L WHERE L.POST_ID = P.POST_ID) AS LIKE_COUNT,
(SELECT COUNT(*) FROM COMMENTS C WHERE C.POST_ID = P.POST_ID) AS REPLY_COUNT
FROM POSTS P;
7. EXISTS 서브쿼리
게시물을 작성한 유저 조회
SELECT U.USER_ID, U.USERNAME
FROM USERS U
WHERE EXISTS (
SELECT 1 FROM POSTS P WHERE P.USER_ID = U.USER_ID
);
게시물을 작성하지 않은 유저 조회
SELECT U.USER_ID, U.USERNAME
FROM USERS U
WHERE NOT EXISTS (
SELECT 1 FROM POSTS P WHERE P.USER_ID = U.USER_ID
);
✅ 요약
- JPA에서 양방향 연관관계 시 편의 메서드를 작성하여 일관성 유지.
- N+1 문제는 Fetch Join으로 해결.
- ManyToMany는 중간 엔티티(Purchase)로 해결.
- Cascade 옵션을 통해 연관 엔티티 자동 삭제.
- SQL에서는 인라인뷰, 스칼라, 연관 서브쿼리, EXISTS 등 다양한 서브쿼리를 통해 복잡한 조회를 효율적으로 처리 가능.