JPA를 사용할 때 성능을 최적화하는 것이 중요합니다. 잘못된 설정이나 무분별한 쿼리 실행은 애플리케이션의 속도를 저하시킬 수 있습니다. 이번 포스팅에서는 JPA의 기본적인 조회 성능 최적화 기법을 소개합니다.
1. FetchType 설정 (EAGER vs LAZY)
JPA에서는 연관된 엔티티를 조회할 때 FetchType을 설정할 수 있습니다.
1) 즉시 로딩 (EAGER)
- 연관된 엔티티를 즉시 조회
- 필요하지 않은 데이터를 불필요하게 로딩하여 성능 저하 가능
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩
private Team team;
}
2) 지연 로딩 (LAZY, 기본값 추천)
- 연관된 엔티티를 실제 사용할 때 조회 (프록시 객체 사용)
- 필요할 때만 데이터를 가져오므로 성능 최적화 가능
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
private Team team;
}
💡 성능 최적화를 위해 가능하면 LAZY 로딩을 기본으로 설정하는 것이 좋습니다.
2. N+1 문제 해결 (Fetch Join & EntityGraph 활용)
1) N+1 문제란?
연관된 엔티티를 조회할 때 추가적인 SELECT 쿼리가 반복적으로 실행되는 문제입니다.
List<Member> members = em.createQuery("SELECT m FROM Member m", Member.class).getResultList();
for (Member member : members) {
System.out.println(member.getTeam().getName()); // Team 조회 시 추가 쿼리 발생 (N번 실행)
}
2) Fetch Join을 활용한 최적화
JOIN FETCH를 사용하여 한 번의 쿼리로 연관된 데이터를 가져올 수 있습니다.
List<Member> members = em.createQuery(
"SELECT m FROM Member m JOIN FETCH m.team", Member.class).getResultList();
💡 Fetch Join을 사용하면 불필요한 추가 쿼리를 방지할 수 있습니다.
3) @EntityGraph 활용
@EntityGraph를 사용하면 Fetch Join과 유사한 효과를 낼 수 있습니다.
@EntityGraph(attributePaths = {"team"})
@Query("SELECT m FROM Member m")
List<Member> findAllWithTeam();
💡 @EntityGraph는 JPQL을 직접 작성하지 않고도 Fetch Join 효과를 낼 수 있는 강력한 방법입니다.
3. Batch Size 설정을 통한 최적화
컬렉션을 조회할 때 @BatchSize를 설정하면 IN 절을 활용하여 한 번에 데이터를 가져올 수 있습니다.
@Entity
public class Team {
@OneToMany(mappedBy = "team")
@BatchSize(size = 10)
private List<Member> members;
}
또는 글로벌 설정으로 적용할 수도 있습니다.
spring.jpa.properties.hibernate.default_batch_fetch_size=100
💡 @BatchSize 설정을 활용하면 N+1 문제를 해결할 수 있습니다.
4. JPQL 대신 Criteria API 또는 QueryDSL 활용
JPQL은 문자열 기반이라 동적 쿼리 작성이 어렵습니다. 이를 해결하기 위해 QueryDSL을 사용할 수 있습니다.
QMember member = QMember.member;
List<Member> members = queryFactory
.selectFrom(member)
.where(member.age.gt(20))
.fetch();
💡 QueryDSL을 사용하면 성능 최적화뿐만 아니라 가독성과 유지보수성도 향상됩니다.
5. 인덱스 및 페이징 최적화
1) 인덱스 활용
- 조회 성능을 높이기 위해 @Index를 추가할 수 있습니다.
@Entity
@Table(indexes = {@Index(name = "idx_member_name", columnList = "name")})
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
}
2) 페이징 시 최적화
setFirstResult()와 setMaxResults()를 활용하면 페이징 쿼리를 최적화할 수 있습니다.
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m ORDER BY m.id", Member.class);
query.setFirstResult(0); // 시작 위치
query.setMaxResults(10); // 최대 조회 개수
List<Member> members = query.getResultList();
💡 인덱스와 페이징을 함께 활용하면 대량 데이터 조회 시 성능을 크게 개선할 수 있습니다.
결론
JPA를 활용할 때 성능 최적화를 위해 다음과 같은 전략을 사용할 수 있습니다.
✅ FetchType.LAZY 사용하여 불필요한 데이터 로딩 방지
✅ Fetch Join과 @EntityGraph 활용하여 N+1 문제 해결
✅ Batch Size 설정을 통해 다중 조회 최적화
✅ QueryDSL 활용하여 가독성과 성능 개선
✅ 인덱스와 페이징 최적화를 통해 대량 데이터 조회 개선
'BackEND > Java' 카테고리의 다른 글
QueryDSL과 JPA 성능 튜닝 전략 (0) | 2025.04.04 |
---|---|
QueryDSL을 활용한 동적 쿼리 작성 및 성능 개선 (0) | 2025.04.03 |
JPA와 QueryDSL 소개 및 비교 (0) | 2025.04.01 |
Spring Batch를 활용한 대용량 데이터 처리 (0) | 2025.03.31 |
프로젝트에서 Redis 캐시 최적화 방법 (0) | 2025.03.30 |