새소식

TIL

[TIL] 230703 <JPA> JpaRepository, QueryDSL

  • -

 

 

  • Repository는 MarkerInterface로 특별한 기능은 없음
  • Repository ~ JpaRepository까지는 @NotRepositoryBean이 붙어있는 인터페이스
    • JpaRepository<Entity,ID> 붙이면 알맞게 프로그래밍 된 SimpleJpaReository 구현체 빈이 등록됨
      • @NotRepositoryBean 상위 인터페이스들의 기능을 포함한 구현체가 프로그래밍됨
        (@NotRepositoryBean = 빈생성 막음)
      • @SpringBootApplication을 통해 자동으로 붙여지는 @EnableJpaRepositoriesJpaRepositoriesRegistrar를 통해서 등록됨!
        • JpaRepositoriesRegistrar 는 ImportBeanDefinitionRegistrar 의 구현체
        • ImportBeanDefinitionRegistrar 는 프로그래밍을 통해 빈을 주입해줌

 

 

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

  • 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가 상승!!!

  • 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

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.