왠만한 Entity에 대한 설명은 https://dean83.tistory.com/277 이곳에 작성해 두었다. 이어서, Entitiy 심화 내용을 정리하고자 한다.
N+1 문제
N+1 문제는 JPA를 이용할 경우 상황에 따라서 쿼리문을 N+1번 날린다는 뜻이다.
상황을 설명하자면,
- A, B 테이블이 있다.
- B 테이블은 A 테이블의 외래키를 갖고 있고, 1:N 관계이다.
- 다수의 B 항목들을 조회하면 (이때 쿼리 1번 발생), JPA에 의해 List 형태로 리턴을 받게 된다.
- List 를 반복문으로 돌면서 A 항목을 조회하게 되면, B의 개수 만큼 쿼리문이 발생한다. (N번)
이는 join을 사용하지 않고 JPA가 동작하기 때문에 발생하는 문제이다.
위의 경우를 JPA 를 사용했을때를 생각해 본다면 다음과 같을 것이다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY) // 다(N) -> 일(1)
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY) // 일(1) -> 다(N)
private List<Member> members = new ArrayList<>();
}
------
@Service
public class MemberService
{
....
// 모든 회원 조회
List<Member> members = memberRepository.findAll();
// 각 회원이 속한 팀 이름을 출력
for (Member m : members) {
System.out.println(m.getTeam().getName());
}
....
}
- 이를 해결하기 위해서는 @Query 어노테이션에서 Fetch join을 활용하면 된다.
- 역방향 조회를 해야 한다.
- Fetch join을 활용하나, N에 해당하는 곳에서 조회를 역으로 한다.
- 역으로 조회를 함으로서 쿼리 결과를 최적화 하여 받는것을 말한다.
- 역방향 조회를 해야 한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m join fetch m.team")
List<Member> findAllWithTeam();
}
- N+1 문제가 있는지 여부를 판단하기 위해선 다음의 조건을 모두 만족할때로 요약할 수 있다.
- 엔티티 관계가 1:N 일때
- N 쪽에서 findAll 등 단건이 아닌 다수의 데이터를 조회 할때 (단건은 해당 안됨)
- 다수의 데이터에서 반복문을 통해 맴버에 접근 할 때
- 반복문은 for 문 같은 명시적인 반복문도 있지만, stream 을 통한 반복 코드도 생각해야함.
- 즉 요약하면, N 에 해당하는 엔티티의 Service 에서 findAll, findByxxx 를 호출하는 코드들을 확인하면 된다.
- Eager loading
- Eager loading은 엔티티 조회시, 연관되어 있는 엔티티들을 바로 같이 불러 오는 경우이다.
- @ManyToOne, @OneToOne 일 경우 Eager loading이 기본값이다.
- N+1 문제의 주요 원인이기도 하다.
- 실제 사용할 일이 잘 없으므로 lazy loading을 하는것이 좋다.
- 따라서 위의 관계일 경우 명시적으로 lazy loading을 설정해 줘도 Eager loading으로 동작 할 수 있다.(양방향일 경우)
- Lazy loading
- 필요할때 쿼리를 날려 조회를 한다.
- 모든 관계에서 이것을 기본으로 설정 해야 한다.
- 예 : @ManyToOne(fetch = FetchType.LAZY)
- 트랜잭션이 종료된 후 해당 컬럼을 조회하려 하면, lazy initialization 오류가 발생하니, 조심해야 한다
- 예를들어, entityA, entityB가 있고, 양방향 관계를 맺고 있다. (OneToOne, OneToMany 등)
- 해당 관계는 Lazy 로딩으로 설정되어 있다.
- Service 에서 entityA를 조회한 후에 Controller 로 리턴을 해줬다.
- Controller 에서 entityA 에 속한 entityB 를 get 하려고 할때 오류가 발생한다.
- 이를 해결하기 위해 application.yaml 의 jpa 설정에서 open-in-view: true 를 설정할 수도 있다. (https://dean83.tistory.com/277 상단부 설정 부분 참조)
- 그러나, N+1 오류를 미리 알아야 하므로, 개발단에서는 false로, 실서버에는 true로 하는게 좋다.
- 되도록이면 서비스에서 DTO로 치환하여 Controller로 변환 해주면 문제가 해결된다.
Cascade, orphanRemoval 옵션
- Cascade 옵션은 , 부모 객체가 삭제될 때 자식객체를 어떻게 관리할지에 대한 설정
- orphanRemoval 은 부모 객체가 삭제되지 않은 상황에서, 부모에서 자식을 삭제할때 실제로 자식 객체를 DB에서 삭제하는 옵션
- 즉, A 라는 클래스에서 맴버변수로 List<B> bList를 가졌다고 생각할때, bList에서 항목을 삭제하게 되면 DB에서 어떻게 처리할지 하는 옵션이다.
'Backend > SpringBoot 이론 부분' 카테고리의 다른 글
| Filter 및 SecurityFilterChain (0) | 2025.12.15 |
|---|---|
| Spring boot의 요청 처리 흐름 (0) | 2025.12.15 |
| SpringBoot 에서 트랜잭션 (0) | 2025.10.01 |
| Proxy (0) | 2025.09.30 |
| Spring Data JPA (0) | 2025.09.29 |