대규모 데이터를 다루는 애플리케이션에서는 성능 최적화가 중요한 과제이다. JPA는 이를 벌크성 수정/삭제 쿼리와 엔티티 그래프라는 기능을 제공한다. 이번 글에서는 두 가지 개념을 설명하고 활용하는 방법을 살펴보겠다.
1. 벌크성 수정/삭제 쿼리
1.1 벌크 연산이란?
벌크 연산은 여러 엔티티를 한 번에 수정하거나 삭제하는 작업으로, JPA에서 제공하는 JPQL을 사용해 실행한다. 이는 성능 최적화를 위해 데이터베이스에 직접 업데이트/삭제 쿼리를 전달하며, 영속성 컨텍스트에 있는 데이터를 무시한다.
1.2 벌크성 수정 쿼리 예제
JPA로 벌크 수정
public int bulkAgePlus(int age) {
int resultCount = em.createQuery(
"update Member m " +
"set m.age = m.age + 1 " +
"where m.age >= :age"
)
.setParameter("age", age)
.executeUpdate();
return resultCount;
}
- JPQL 사용 : update 쿼리를 통해 age가 특정 값 이상인 회원들의 나이를 한 번에 증가시킨다.
- executeUpdate() : 수정된 엔티티 수를 반환한다.
스프링 데이터 JPA로 벌크 수정
@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
- @Modifying : DML 쿼리(update, delete) 임을 나타낸다. 없으면 예외 발생.
- clearAutomatically 옵션 : 벌크 연산 후 영속성 컨텍스트를 자동 초기화 시킨다.
1.3 벌크 연산 테스트
@Test
public void bulkUpdate() {
// given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 19));
memberRepository.save(new Member("member3", 20));
memberRepository.save(new Member("member4", 23));
// when
// em.clear() @Modifying(clearAutomatically = true)이기 때문에 굳이 clear 안해도됨.
int resultCount = memberRepository.bulkAgePlus(20);
// then
assertThat(resultCount).isEqualTo(2); // 수정된 엔티티 수 검증
}
1.4 벌크 연산 후 주의사항
- 영속성 컨텍스트와 DB 간의 불일치
- 벌크 연산은 영속성 컨텍스트를 무시하고 실행된다.
- 영속성 컨텍스트를 무시하기 때문에 DB는 업데이트가 되었지만 영속성 컨텍스트 안에 있는 1차 캐시는 수정되기 전으로 그대로 남아있어서 항상 주의 해야함.
- DB 상태와 영속성 컨텍스트 상태가 달라질 수 있다.
- 벌크 연산은 영속성 컨텍스트를 무시하고 실행된다.
- 권장 방안
- 벌크 연산을 먼저 실행 : 영속성 컨텍스트에 인티티가 없는 상태에서 실행
- 컨텍스트 초기화 : 벌크 연산 후 em.clear() 또는 @Modifying(clearAutomatically = true)를 사용.
2. 엔티티 그래프(Entity Graph)
2.1 엔티티 그래프란?
엔티티 그래프는 연관된 엔티티를 한 번에 가져오는 기능으로, JPQL의 페치 조인과 유사하다. 여러 연관된 데이터를 조회할 때 발생하는 N+1 문제를 해결하는데 유용하다.
2.2 JPQL 페치 조인
페치 조인을 사용하면 연관된 엔티티를 한 번의 SQL로 가져온다.
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
- left join fetch : 회원과 팀을 조인하며 팀 데이터를 즉시 로딩한다.
- 장점 : 연관된 데이터를 한 번에 가져와 N+1 문제 해결
2.3 엔티티 그래프
스프링 데이터 JPA는 엔티티 그래프를 통해 JPQL 없이도 연관 데이터를 쉽게 페치 조인할 수 있도록 도와준다.
공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
- attributePaths: 페치 조인할 연관 엔티티를 지정한다.
- team 필드를 즉시 로딩한다.
JPQL + 엔티티 그래프
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
메서드 이름 쿼리와 함께 사용
@EntityGraph(attributePaths = {"team"})
List<Members> findByUsername(String username);
2.4 엔티티 그래프 테스트
@Test
public void findMemberLazy() {
// given
Team teamA = new Team("teamA");
teamRepository.save(teamA);
memberRepository.save(new Member("member1", 10, teamA));
em.flush();
em.clear();
// when
List<Member> members = memberRepository.findAll();
// then
for (Member member : members) {
System.out.println(member.getTeam().getName()); // 페치 조인으로 팀 데이터 로드
}
}'Back-End > JPA' 카테고리의 다른 글
| 트러블 슈팅 : 게시글 조회 시 조회수가 증가하지 않는 문제 (0) | 2025.02.04 |
|---|---|
| Spring Data JPA : Auditing (1) | 2024.12.18 |
| Spring Data Jpa : 페이징과 정렬 (1) | 2024.12.18 |
| Spring DataJpa : 쿼리 메서드 기능 (0) | 2024.12.18 |
| 프록시와 연관관계 관리(JPA) (0) | 2024.12.17 |