일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 1차원 DP
- 2차원 dp
- 99클럽
- @BeforeAll
- @BeforeEach
- @Builder
- @Entity
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Query
- @Table
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- api gateway 설계
- api gateway 필터
- ApplicationEvent
- assertThat
- async/await
- AVG
- AWS
- aws eks
- AWS 프리티어
- Azure
- bind
- bitnami kafka
- Today
- Total
기록
[자바 ORM 표준 JPA 프로그래밍] 스프링 데이터 JPA 기능/쿼리 메서드, 페이징과 정렬, 벌크 연산 (12장-1) 본문
시작하면서
아래 내용은 "자바 ORM 표준 JPA 프로그래밍" 책을 읽고 정리한 내용입니다. 스프링 데이터 JPA는 Java Persistence API(JPA)를 더 쉽게 사용할 수 있도록 도와주는 모듈로, 데이터베이스와의 상호작용을 효율적으로 처리하는 데 큰 역할을 합니다. 이 모듈은 CRUD(생성, 조회, 수정, 삭제) 작업을 위한 공통 인터페이스를 제공하며, 개발자가 리포지토리를 구현할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 해당 구현체를 동적으로 생성하고 주입해줍니다. 이를 통해 데이터 접근 계층을 간결하게 유지할 수 있고, 생산성을 크게 높일 수 있습니다.
공통 인터페이스 기능
스프링 데이터 JPA는 CRUD 작업을 위한 공통 메서드를 제공합니다. 대표적인 메서드는 다음과 같습니다:
save()
: 엔티티 저장 또는 업데이트delete()
: 엔티티 삭제findOne()
/findById()
: 엔티티 단건 조회findAll()
: 모든 엔티티 조회
이 기본적인 기능들만으로도 대부분의 데이터 조작 작업을 쉽게 처리할 수 있습니다. 반복적인 SQL 작성의 부담에서 벗어나 비즈니스 로직에 집중할 수 있게 해줍니다.
쿼리 메소드 기능
스프링 데이터 JPA의 가장 매력적인 기능 중 하나는 쿼리 메소드입니다. 메서드 이름만으로 자동으로 쿼리를 생성할 수 있는 기능인데요, 예를 들어 findByUsername(String username)
이라는 메서드를 작성하면 username
값을 조건으로 조회하는 쿼리가 자동 생성됩니다.
복잡한 쿼리가 필요할 때는 @Query
어노테이션을 사용해 직접 정의할 수 있습니다.
@Query("SELECT m FROM Member m WHERE m.age = :age")
List<Member> findMembersByAge(@Param("age") int age);
쿼리 메소드에는 다양한 키워드를 사용할 수 있습니다:
- 조회:
find...
- 존재 확인:
exists...
- 삭제:
delete...
- 개수 확인:
count...
- 제한:
limit...
이러한 키워드를 사용하면 다양한 방식으로 데이터를 손쉽게 조회하고 관리할 수 있습니다.
네임드 쿼리와 파라미터 바인딩
네임드 쿼리
@NamedQuery
를 이용하면 엔티티 클래스에 쿼리를 미리 정의해 둘 수 있습니다. 이를 통해 애플리케이션 실행 시점에 문법 오류를 잡을 수 있으며, 코드 재사용성도 높일 수 있습니다.
@Entity
@NamedQuery(name = "Member.findByUsername", query = "SELECT m FROM Member m WHERE m.username = :username")
public class Member {
// 필드 및 메서드
}
리포지토리에서는 간단하게 해당 쿼리를 호출할 수 있습니다.
List<Member> members = memberRepository.findByUsername("john");
파라미터 바인딩
스프링 데이터 JPA는 위치 기반과 이름 기반 파라미터 바인딩을 모두 지원합니다. 이름 기반 바인딩을 사용하면 가독성이 더 좋고 유지보수에도 유리합니다.
@Query("SELECT m FROM Member m WHERE m.age = :age")
List<Member> findMembersByAge(@Param("age") int age);
페이징과 정렬
특별한 반환 타입
페이징과 정렬 기능도 매우 간단하게 사용할 수 있습니다. Page
, Slice
, List
는 각각의 특성과 사용 목적이 다릅니다:
- Page: 추가적으로 전체 데이터의 개수를 조회하는
count
쿼리를 실행하여 총 페이지 수, 현재 페이지, 전체 요소 수 등의 정보를 제공합니다. 페이징 처리와 함께 전체 결과 개수를 알고 싶을 때 사용합니다. - Slice:
count
쿼리 없이 다음 페이지 여부만 확인할 수 있습니다. 내부적으로limit + 1
을 조회하여 다음 페이지가 있는지 확인합니다. 전체 개수는 필요 없고, 다음 페이지 존재 여부만 필요한 경우에 사용합니다. - List (자바 컬렉션): 단순히 페이징 없이 결과만 반환합니다. 추가적인 페이징 정보가 필요 없을 때 사용합니다.
페이징과 정렬 사용 예제
Page<Member> findByUsername(String username, Pageable pageable); // count 쿼리 사용
Slice<Member> findByUsername(String username, Pageable pageable); // count 쿼리 사용 안함, 다음 페이지 존재 여부만 확인
List<Member> findByUsername(String username, Pageable pageable); // count 쿼리 사용 안함, 결과만 반환
List<Member> findByUsername(String username, Sort sort); // 정렬 조건만 사용
페이징 구성 요소
페이징의 구성 요소는 다음과 같습니다: Pageable
인터페이스를 활용해 페이징을 적용할 수 있으며, @PageableDefault
어노테이션으로 기본 설정을 지정할 수 있습니다.
- Pageable 인터페이스: 페이지 번호와 페이지 크기 등을 지정하기 위해 사용합니다.
Pageable pageable = PageRequest.of(0, 10); // 첫 번째 페이지(0부터 시작), 페이지 크기 10
- Page 객체: 조회된 결과를 포함하며, 총 페이지 수, 현재 페이지, 전체 요소 수 등의 메타 정보를 제공합니다.
Page<Member> resultPage = memberRepository.findByUsername("john", pageable); System.out.println("Total pages: " + resultPage.getTotalPages()); System.out.println("Total elements: " + resultPage.getTotalElements()); System.out.println("Current page content: " + resultPage.getContent());
- 정렬(Sort): 특정 필드를 기준으로 정렬할 수 있습니다.
Sort
객체를 사용하여 정렬 방향과 정렬 필드를 지정할 수 있습니다. Sort sort = Sort.by(Sort.Direction.DESC, "age"); Pageable sortedPageable = PageRequest.of(0, 10, sort);
페이징에서 주의해야 할 것은 페이지 번호가 0부터 시작한다는 점입니다.
Page
객체는 DTO로 안전하게 변환하기 위해 map()
메서드를 사용할 수 있습니다. 이를 통해 엔티티가 직접 외부에 노출되는 것을 방지하고, 보안을 강화할 수 있습니다.
벌크성 수정 쿼리
벌크 연산과 영속성 컨텍스트 초기화의 필요성
대량의 데이터 수정이나 삭제 작업에는 @Modifying
어노테이션을 사용합니다. 벌크 연산 후에는 영속성 컨텍스트와 DB의 상태가 다를 수 있으므로, 영속성 컨텍스트를 초기화하는 것이 중요합니다.
// 벌크 업데이트 수행
@Modifying(clearAutomatically = true)
@Query("UPDATE Member m SET m.age = m.age + 1 WHERE m.age >= 15")
int bulkUpdateAge();
// 벌크 업데이트 후 캐시 초기화
entityManager.clear();
// 변경된 값 반영 확인을 위한 조회
Member updatedMember = memberRepository.findByUsername("member2");
System.out.println("Updated Member Age: " + updatedMember.getAge()); // 출력: Updated Member Age: 21
벌크 연산 후 발생하는 불일치 문제
벌크 연산으로 데이터베이스(DB)의 값을 직접 수정한 경우, 영속성 컨텍스트에는 여전히 이전 값이 남아 있을 수 있습니다. 이로 인해 DB의 값과 캐시(영속성 컨텍스트)의 값이 일치하지 않는 상황이 발생할 수 있습니다. 벌크 업데이트는 DB에 즉시 반영되지만, 캐시에는 해당 업데이트 정보가 전달되지 않기 때문에, DB의 최신 상태와 캐시된 상태가 불일치하게 됩니다.
벌크 연산 과정 설명
이 과정을 순서대로 설명하면 다음과 같습니다:
- 벌크 업데이트 수행 직전
- 캐시: (member1, 10), (member2, 20)
- DB: (member1, 10), (member2, 20)
- 벌크 업데이트 수행 (DB 즉시 반영)
- 캐시: (member1, 10), (member2, 20)
- DB: (member1, 11), (member2, 21)
- 데이터 조회 (캐시로부터 조회)
- 캐시에서 값을 가져오므로, DB의 실제 값이 아닌, 여전히 (member1, 10), (member2, 20) 상태로 조회됩니다. 이로 인해 실제 DB의 값과 차이가 발생하게 됩니다. 따라서 캐시를 초기화할 필요가 있습니다.
- 캐시 초기화
entityManager.clear()
를 통해 캐시를 초기화합니다.
- 데이터 조회 (캐시에 데이터가 없으므로 DB에서 조회 후 캐시에 업로드)
- 캐시에 데이터가 없기 때문에 DB에서 조회하게 되며, 이후 캐시에 최신 상태로 저장됩니다.
- 캐시 및 DB: (member1, 11), (member2, 21)
JPA Hint & Lock
스프링 데이터 JPA는 성능 최적화를 위한 JPA 힌트를 제공합니다. 예를 들어, 읽기 전용 쿼리로 지정하여 변경 추적을 방지할 수 있습니다.
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);
이 힌트는 엔티티를 변경하지 않는 읽기 전용 작업에서 불필요한 비용을 줄이는 데 유용합니다.
마무리
스프링 데이터 JPA는 JPA를 쉽게 사용할 수 있도록 도와주는 강력한 도구입니다. 공통 인터페이스와 쿼리 메서드, 페이징, 벌크 연산, JPA 힌트 등 다양한 기능을 활용하여 데이터를 더욱 쉽게 관리하고 비즈니스 로직에 집중할 수 있습니다.
'교육 > 책' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 일반적인 트랜잭션 관리와 뷰에서 필요한 데이터에 접근(13장) (0) | 2024.12.07 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 스프링 데이터 JPA/성능 최적화 (12장-2) (0) | 2024.12.07 |
[자바 ORM 표준 JPA 프로그래밍] JPA 값 타입 - 기본 값 타입, 임베디드 타입, 값 타입 컬렉션 (9장) (0) | 2024.12.01 |
[자바 ORM 표준 JPA 프로그래밍] JPA의 프록시와 연관관계 관리: 지연 로딩, 영속성 전이, 고아 객체 (8장) (0) | 2024.12.01 |
[자바 ORM 표준 JPA 프로그래밍] JPA 복합 키 매핑: @IdClass vs @EmbeddedId, 조인컬럼과 조인테이블 (7장-2) (0) | 2024.12.01 |