TIL
[TIL] 230703 <JPA> JpaRepository, QueryDSL
- -
[JpaRepository 쿼리 기능 : 테이블 객체로 자동 쿼리 생성하기]
- Repository는 MarkerInterface로 특별한 기능은 없음
- Repository ~ JpaRepository까지는 @NotRepositoryBean이 붙어있는 인터페이스
- JpaRepository<Entity,ID> 붙이면 알맞게 프로그래밍 된 SimpleJpaReository 구현체 빈이 등록됨
- @NotRepositoryBean된 상위 인터페이스들의 기능을 포함한 구현체가 프로그래밍됨
(@NotRepositoryBean = 빈생성 막음) - @SpringBootApplication을 통해 자동으로 붙여지는 @EnableJpaRepositories의 JpaRepositoriesRegistrar를 통해서 등록됨!
- JpaRepositoriesRegistrar 는 ImportBeanDefinitionRegistrar 의 구현체
- ImportBeanDefinitionRegistrar 는 프로그래밍을 통해 빈을 주입해줌
- @NotRepositoryBean된 상위 인터페이스들의 기능을 포함한 구현체가 프로그래밍됨
- JpaRepository<Entity,ID> 붙이면 알맞게 프로그래밍 된 SimpleJpaReository 구현체 빈이 등록됨
JpaRepository 효율적으로 사용하는 방법
1. Optional 제거하기
- Spring Data JPA의 findByXX 메서드는 기본적으로 Optional을 반환
- 이로 인해 비즈니스 로직에서 Optional 처리를 위한 추가적인 작업이 필요하게 되는데, 이럴 때 default 메서드를 활용 가능
public interface UserRepository extends JpaRepository<User, Long> {
// Default 메소드를 사용하여 findById의 Optional을 내부적으로 처리
default User findUserById(Long id) {
return findById(id).orElseThrow(() -> new DataNotFoundException("User not found with id: " + id));
}
}
2. 메서드명 간소화하기
- Spring Data JPA를 사용하다보면 복잡한 쿼리 때문에 메서드명이 길어져 가독성을 해치는 경우가 있음
- 이럴 때도 default 메서드를 활용하면 긴 메서드명을 간결하고 명확하게 표현 가능
public interface ProductRepository extends JpaRepository<Product, Long> {
// 기존의 긴 쿼리 메소드
List<Product> findAllByCategoryAndPriceGreaterThanEqualAndPriceLessThanEqualOrderByPriceAsc(String category, BigDecimal minPrice, BigDecimal maxPrice);
// Default 메소드를 사용하여 간결한 메소드명 제공
default List<Product> findProductsByCategoryAndPriceRange(String category, BigDecimal minPrice, BigDecimal maxPrice) {
return findAllByCategoryAndPriceGreaterThanEqualAndPriceLessThanEqualOrderByPriceAsc(category, minPrice, maxPrice);
}
}
3. 비즈니스 로직 통합
- 여러 기본 제공 메서드를 하나의 고차 작업으로 결합가능
- 다만 Spring Data JPA의 Repository는 Data Access Layer의 일부로, 데이터베이스와의 상호작용만을 담당하는 것이 일반적이기 때문에 이 부분은 서비스 레이어에서 처리하는 것이 일반적!
public interface UserRepository extends JpaRepository<User, Long> {
// 사용자 ID로 사용자를 찾고, 존재할 경우 연락처 정보를 업데이트하는 메소드
default void updateUserContact(Long userId, String newContact) {
findById(userId).ifPresent(user -> {
user.setContact(newContact);
save(user);
});
}
}
[Pageable & Sorting]
Pageable
- Pageable 인터페이스를 구현한 PageRequest 객체를 만들거나 얻음
- PageRequest 객체를 repository 메소드에 인자로 전달
- PageRequest 객체는 요청된 페이지 숫자와 페이지 사이즈를 넘김으로서 만듦 (페이지 숫자는 0부터 시작)
// 첫 페이지 (페이지 사이즈 = 2)
Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
// 두번째 페이지 (페이지 사이즈 = 5)
Pageable secondPageWithFiveElements = PageRequest.of(1, 5);
// 페이지 사용
List<Product> allTenDollarProducts =
productRepository.findAllByPrice(10, secondPageWithFiveElements);
- findAll(Pageable pageable) 메소드는 기본적으로 Page 객체를 리턴하지만
- 커스텀 메소드를 통해 페이지네이션된 데이터를 Page , Slice 또는 List 의 타입으로 받을 수 있음
- Page 인스턴스는 Product의 목록 뿐 아니라 페이징할 수 있는 전체 목록의 숫자도 알고 있음
- 이를 실행하기 위해 추가적으로 쿼리 작업이 들어감
- 이러한 작업에 대한 비용을 방지하기 위해, 우리는 대신 Slice나 List로 반환 받을 수 있음
- Slice는 단지 다음 slice가 가능한지 아닌지만 알고 있음
Sorting
- 쿼리 결과를 정렬하기 위해서 Sort 객체를 메소드에 전달
- 정렬과 페이지네이션을 둘다 하고 싶다면 정렬에 대한 디테일 정보를 PageRequest 객체에 전달!
Pageable sortedByName = PageRequest.of(0, 3, Sort.by("name"));
Pageable sortedByPriceDesc = PageRequest.of(0, 3, Sort.by("price").descending());
Pageable sortedByPriceDescNameAsc = PageRequest.of(0, 5, Sort.by("price").descending().and(Sort.by("name")));
- 페이징 없이 정렬만 하려는 경우 findAll(Sort sort) 메서드와 같이 Sort 객체만 파라미터로하는 메서드를 작성하면 됨
Page<Product> allProductsSortedByName = productRepository.findAll(Sort.by("name").accending());
- CRUD에 관련된 연산이 문자열로 이루어진 경우, Run time시에 해당 에러를 확인가능
- Compile 단계에서 Type-Check가 불가능하여, 장애 Risk가 상승!!!
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
[QueryDSL]
- JPQL과 같이, 쿼리문을 문자열로 작성하여, Compile 시에 Type-Check가 되지 않았던 문제를 해결하기 위해 나온 프레임워크
- 정적 타입을 이용하여, SQL과 같은 쿼리를 코드 형태로 생성할 수 있도록 해주는 오픈소스 빌더 API
- SQL문을 직접 하드코딩 하는 것이 아닌, 코드 형태로 작성하는 것이 특징
- 쿼리가 문자열 형태가 아닌, 함수 형태로 조합되어 최종 결과를 반환
- @Entity로 등록된 모든 클래스에 대한 Q-객체를 만들고, 그 안에 멤버 변수로 정의된 컬럼들에 대한 연산들을 메서드 형태로 Compile-time에 생성
- 즉, Compile-time에 에러가 식별되기 때문에 장애 Risk를 크게 줄일 수 있음
- Entity의 매핑정보를 활용하여 쿼리에 적합하도록 쿼리 전용 클래스(Q클래스)로 재구성해주는 기술
- JPAQueryFactory을 통한 Q클래스를 활용할 수 있는 기능들을 제공
- JPAQueryFactory
- 재구성한 Q클래스를 통해 문자열이 아닌 객체 또는 함수로 쿼리를 작성하고 실행하게 해주는 기술
- JPAQueryFactory 사용을 위해 JPAQueryFactory에 entityManager를 주입해서 Bean으로 등록해줘야 함
@Configuration
public class JPAConfiguration {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
- Spring boot 3.x에서 QueryDSL 연동하기
- build.gradle의 dependencies에 아래와 같이 추가
//QueryDSL 적용을 위한 의존성 (SpringBoot3.0 부터는 jakarta 사용해야함)
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
Q-객체
- JPAAnnotationProcessor가 컴파일 시점에 @Entity와 @Embeddedable과 같은 어노테이션이 걸린 클래스에 대해, 미리 생성하는 쿼리 객체
- 해당 객체에 쿼리문을 메서드 형태로 호출하여 SQL문을 짜듯이 코드로 쿼리문 작성이 가능
'TIL' 카테고리의 다른 글
<5조 Trell5> Spring 심화 프로젝트 KPT 회고 (0) | 2024.07.16 |
---|---|
<5조 Trell5> Spring 심화 프로젝트 S.A (0) | 2024.07.10 |
[TIL] 230702 <JPA> JDBC (0) | 2024.07.02 |
[TIL] 230628 <AWS> RDS (0) | 2024.06.28 |
[TIL] 230627 <AWS> EC2, ELB (0) | 2024.06.27 |
Contents
소중한 공감 감사합니다