성능 최적화
- N+1 문제란 무엇이며, 이를 해결하기 위한 방법은 무엇인가요?
- @Query와 NamedQuery의 차이점을 설명해주세요.
- JPA에서 캐싱 전략은 어떻게 구현할 수 있나요?
- JPA에서 Batch Insert/Update를 사용하는 방법은 무엇인가요?
N+1 문제란 무엇이며, 이를 해결하기 위한 방법은 무엇인가요?
N+1 문제란?
- jpa에서 연관된 data를 가져올때 FetchType.Lazy로 가져올때 발생하는 성능 문제
- 하나의 entity를 조회할때 --> 연관된 entity를 조회하기 위해 N번의 추가 쿼리가 발생하는 문제
List<Team> teams = em.createQuery("SELECT t FROM Team t", Team.class).getResultList();
for (Team team : teams) {
System.out.println(team.getMembers().size());
// 지연 로딩으로 인해 각 팀의 멤버 조회 쿼리 발생
}
1) SELECT * FROM Team (1개의 쿼리)
2) 각 팀의 멤버를 조회하는 쿼리가 팀의 수만큼 발생
(SELECT * FROM Member WHERE team_id = ?), 총 N개의 쿼리
해결방법
Fetch Join 사용
JPQL에서 JOIN FETCH를 사용하여 연관된 엔티티를 한 번의 쿼리로 가져옵니다
즉 개발자가 sql 작성하는거임
List<Team> teams = em.createQuery(
"SELECT t FROM Team t JOIN FETCH t.members", Team.class).getResultList();
@EntityGraph 활용
jpa에서 연관된 data를 로딩할때 연관된 필드를 즉시 로딩(EAGER)하도록 설정
@EntityGraph(attributePaths = {"members"})
@Query("SELECT t FROM Team t")
List<Team> findAllWithMembers();
QueryDSL
타입 안전한 쿼리 작성 라이브러리
JPQL을 대체하여 Fetch Join, sub Query등을 쉽게 작성할 수 있다.
QueryDSL- Fetch Join 사용
QTeam team = QTeam.team;
QMember member = QMember.member;
public List<Team> fetchTeamsWithMembers(EntityManager em) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
List<Team> teams = queryFactory.selectFrom(team)
.leftJoin(team.members, member).fetchJoin() // Fetch Join 사용
.fetch();
return teams;// 이 쿼리는 Team과 Member를 한 번의 쿼리로 가져옵니다.
}
SELECT t.*, m.*
FROM team t
LEFT JOIN member m ON t.id = m.team_id;
QueryDSL- Batch Fetch 사용
// Batch Fetching 설정
spring.jpa.properties.hibernate.default_batch_fetch_size=100
QTeam team = QTeam.team;
public List<Team> fetchTeams(EntityManager em) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
// 단순 팀 조회 (Batch Fetching 설정에 따라 멤버는 별도로 로드)
List<Team> teams = queryFactory.selectFrom(team).fetch();
return teams; // 연관된 멤버는 Batch Size에 따라 추가 쿼리 발생
}
1) SELECT * FROM team;
2) SELECT * FROM member WHERE team_id IN (?, ?, ?, ...); 각 팀의 멤버 데이터를 한 번에 로드 (Batch Size에 따라 처리)
Batch Size 설정
1:N 관계에서 여러 연관 엔티티를 한 번의 쿼리로 조회합니다.
spring.jpa.properties.hibernate.default_batch_fetch_size=100
@Query와 NamedQuery의 차이점을 설명해주세요.
@Query
method에서 선언
- runtime에 동적으로 쿼리 실행
- 특정 쿼리를 빠르고 간편하게 사용 가능
@Query("SELECT u FROM User u WHERE u.age > :age")
List<User> findUsersByAge(@Param("age") int age);
NamedQuery
entity class에 선언
- 컴파일 검증 가능
- 재사용 가능
@Entity
@NamedQuery(
name = "User.findByAge",
query = "SELECT u FROM User u WHERE u.age > :age"
)
public class User {
@Id
private Long id;
private String name;
private int age;
}
List<User> users = em.createNamedQuery("User.findByAge", User.class)
.setParameter("age", 20)
.getResultList();
JPA에서 캐싱 전략은 어떻게 구현할 수 있나요?
1차 캐시
jpa는 기본적으로 1차 캐시를 제공
동일한 persistence context 내에서 조회된 entity는 캐시에 저장된다, 따라서 db 조회를 최소화 한다
2차 캐시
persistence context를 벗어나도 entity를 캐싱하여 성능을 개선함
hibernate를 사용할 경우, 2차 캐시 제공 라이브러리는 EhCache, Caffeine을 설정해야한다
-- EhCache 설정
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
spring.cache.jcache.config=classpath:ehcache.xml
-- EhCache 설정 파일
<cache xmlns="http://www.ehcache.org/v3">
<cache alias="default">
<heap unit="entries">1000</heap>
</cache>
</cache>
JPA에서 Batch Insert/Update를 사용하는 방법은 무엇인가요?
batch 설정을 해야, insert/update 쿼리를 한번에 실행한다
spring.jpa.properties.hibernate.jdbc.batch_size=30
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
public void batchInsert(EntityManager em) {
for (int i = 1; i <= 100; i++) {
User user = new User();
user.setName("User " + i);
user.setAge(20 + i);
em.persist(user);
if (i % 30 == 0) { // 30개마다 플러시
em.flush();
em.clear();
}
}
}
'학습 기록 (Learning Logs) > CS Study' 카테고리의 다른 글
JPA-troubleShooting (0) | 2024.12.12 |
---|---|
JPA-advanced-question (0) | 2024.12.12 |
JPA-features (0) | 2024.12.12 |
JPA-mapping (0) | 2024.12.12 |
JPA-default (0) | 2024.12.12 |