JPQL
→ JPA Query Language의 줄임말로 JPA에서 사용할 수 있는 쿼리를 의미
→ SQL과 매우 비슷한 문법을 가짐
→ SQL에서는 테이블이나 칼럼의 이름을 사용하는 것과 달리 매핑된 엔티티 이름과 필드 이름을 사용
리포지토리는 JpaRepository를 상속받는 것만으로도 다양한 CRUD 메서드 제공
but 기본 메서드들은 식별자 기반으로 생성되기 때문에 별도의 메서드를 정의해서 사용하는 경우가 많음
→ 이때 간단한 쿼리문 작성을 위해 사용되는 것이 쿼리 메서드
- 쿼리 메서드는 크게 동작을 결정하는 주제(Subject)와 서술어(Predicate)로 구분
쿼리 메서드 생성
주제: 'find···By', 'exists···By'와 같은 키워드로 정함
서술어: 검색 및 정렬 조건을 지정하는 영역으로 'By'가 서술어의 시작을 나타내는 구분자 역할
* 서술어에 들어가는 엔티티의 속성 식(Expression)은 엔티티에서 관리하고 있는 속성(필드)만 참조 가능
// (리턴 타입) + {주제 + 서술어(속성)} 구조의 메서드
List<Person> findByLastnameAndEmail(String lastName, String email);
쿼리 메서드의 주제 키워드
1. find···By → 조회 기능 수행
Optional<Product> findByNumber(Long number);
List<Product> findAllByName(String name);
Product queryByNumber(Long number);
2. exists···By → 특정 데이터의 존재 확인, 리턴 타입은 boolean
boolean existsByNumber(Long number);
3. count···By → 조회 쿼리 수행 후 쿼리 결과로 나온 레코드 개수 리턴
long countByName(String name);
4. delete···By, remove···By → 삭제 쿼리 수행
void deleteByNumber(Long number);
long removeByName(String name);
5. ···First<number> ···, ···Top<number>··· → 쿼리를 통해 조회된 결괏값의 개수 제한
List<Product> findFirst5ByName(String name);
List<Product> findTop10ByName(String name);
쿼리 메서드의 조건자 키워드
1. Is, Equals → 값의 일치를 조건으로 사용
Product findByNumberIs(Long number);
Product findByNumberEquals(Long number);
2. (Is)Not → 값의 불일치를 조건으로 사용
Product findByNumberIsNot(Long number);
Product findByNumberNot(Long number);
3. (Is)Null, (Is)NotNull → 값이 null인지 검사
List<Product> findByUpdatedAtNull();
List<Product> findByUpdatedAtIsNull();
List<Product> findByUpdatedAtNotNull();
List<Product> findByUpdatedAtIsNotNull();
4. (Is)True, (Is)False → boolean 타입으로 지정된 칼럼값 확인
Product findByisActiveTrue();
Product findByisActiveIsTrue();
Product findByisActiveFalse();
Product findByisActiveIsFalse();
5. And, Or → 여러 조건을 묶음
Product findByNumberAndName(Long number, String name);
Product findByNumberOrName(Long number, String name);
6. (Is)GreaterThan, (Is)LessThan, (Is)Between → 숫자나 datetime 칼럼을 대상으로 한 비교 연산에 사용
List<Product> findByPriceIsGreaterThan(Long price);
List<Product> findByPriceGreaterThan(Long price);
List<Product> findByPriceGreaterThanEqual(Long price);
List<Product> findByPriceIsLessThan(Long price);
List<Product> findByPriceLessThan(Long price);
List<Product> findByPriceLessThanEqual(Long price);
List<Product> findByPriceIsBetween(Long lowPrice, Long highPrice);
List<Product> findByPriceBetween(Long lowPrice, Long highPrice);
7. (Is)StartingWith(==StartsWith), (Is)EndingWith(==EndsWith), (Is)Containing(==Contains), (Is)Like
→ 칼럼값에서 일부 일치 여부를 확인
List<Product> findByNameLike(String name);
List<Product> findByNameIsLike(String name);
List<Product> findByNameContains(String name);
List<Product> findByNameContaining(String name);
List<Product> findByNameIsContaining(String name);
List<Product> findByNameStartsWith(String name);
List<Product> findByNameStartingWith(String name);
List<Product> findByNameIsStartingWith(String name);
List<Product> findByNameEndsWith(String name);
List<Product> findByNameEndingWith(String name);
List<Product> findByNameIsEndingWith(String name);
정렬 처리하기 - ORDER BY 구문 사용
// Asc: 오름차순, Desc: 내림차순
List<Product> findByNameOrderByNumberAsc(String name);
List<Product> findByNameOrderByNumberDesc(String name);
// 여러 정렬 기준 사용 시 And 붙이지 않음
List<Product> findByNameOrderByPriceAscStockDesc(String name);
// 매개변수 활용한 쿼리 정렬
List<Product> findByName(String name, Sort sort);
// 쿼리 메서드에 Sort 객체 전달
productRepository.findByName("펜", Sort.by(Order.asc("price")));
페이징 처리 - Page와 Pageable 사용
→ 페이징이란 데이터베이스의 레코드를 개수로 나눠 페이지를 구분하는 것을 의미
// 페이징 처리를 위한 쿼리 메서드 예시
Page<Product> findByName(String name, Pageable pageable);
// 페이징 쿼리 메서드 호출 방법
Page<Product> productPage = productRepository.findByName("펜", PageRequest.of(0, 2));
// 페이지 객체의 데이터 출력
System.out.println(productPage.getContent());
@Query 어노테이션을 사용해 JPQL 형식의 쿼리문 작성
→ FROM 뒤에서 엔티티 타입 지정(및 별칭 생성)
→ WHERE문에서 조건 지정
// @Query 어노테이션을 사용하는 메서드
@Query("SELECT p FROM Product AS p WHERE p.name = ?1")
List<Product> findByName(String name);
// @Query 어노테이션과 @Param 어노테이션을 사용한 메서드
@Query("SELECT p FROM Product p WHERE p.name = :name")
List<Product> findByNameParam(@Param("name") Strin name);
// 특정 칼럼만 추출하는 쿼리
@Query("SELECT p.name, p.price, p.stock FROM Product p WHERE p.name = :name")
List<Object[]> findByNameParam2(@Param("name") String name);
JPQL 쿼리의 한계: 쿼리의 문자열이 잘못된 경우 애플리케이션이 실행된 후 로직이 실행되고 나서야 오류 발견 가능
→ 이로 발생하는 문제: 개발 환경에서는 문제가 없는 것처럼 보이다가 실제 운영 환경에 애플리케이션 배포하고 나서 오류 발견
→ 이를 해결하기 위해 사용되는 것: QueryDSL
QueryDSL
정적 타입을 이용해 SQL과 같은 쿼리를 생성할 수 있도록 지원하는 프레임워크
문자열이나 XML 파일을 통해 쿼리 작성하는 대신 플루언트(Fluent) API를 활용해 쿼리 생성 가능
QueryDSL의 장점
QueryDSL을 사용하기 위한 프로젝트 설정
1. pom.xml에 의존성 추가
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
</dependencies>
2. 태그에 APT 플러그인 추가
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
<options>
<querydsl.entityAccessors>true</querydsl.entityAccessors>
</options>
</configuration>
</execution>
</executions>
</plugin>
3. 메이븐의 compile 단계를 클릭해 빌드 작업 수행
4. IntelliJ IDEAD에서 [Ctrl + Alt + Shift + S]를 눌러 설정 창 열고 [Modules] 탭 클릭
5. generatead-sources를 눌러 [Mark as] 항목에 있는 [Sources]를 눌러 IDEA에서 소스 파일로 인식할 수 있게 설정
기본적인 QueryDSL 사용하기
// JPAQuery를 활용한 QueryDSL 테스트 코드
@PersistenceContext
EntityManager entityManager;
@Test
void queryDslTest() {
JPAQuery<Product> query = new JPAQuery(entityManager);
QProduct qProduct = QProduct.product;
List<Product> productList = query
.from(qProduct)
.where(qProduct.name.eq("펜"))
.orderBy(qProduct.price.asc())
.fetch();
for (Product product : productList) {
System.out.println("----------------");
System.out.println();
System.out.println("Product Number : " + product.getNumber());
System.out.println("Product Name : " + product.getName());
System.out.println("Product Price : " + product.getPrice());
System.out.println("Product Stock : " + product.getStock());
System.out.println();
System.out.println("----------------");
}
}
QuerydslPredicateExecutor, QuerydslRepositorySupport 활용
1. QuerydslPredicateExecutor 인터페이스
public interface QProductRepository extends JpaRepository<Product, Long>,
QuerydslPredicateExecutorProduct> {
}
// 제공 메서드
Optional<T> findOne(Predicate predicate);
Iterable<T> findAll(Predicate predicate);
Iterable<T> findAll(Predicate predicate, Sort sort);
Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);
Iterable<T> findAll(OrderSpecifier<?>... orders);
Page<T> findAll(Predicate predicate, Pageable pageable);
long count(Predicate predicate);
boolean exists(Predicate predicate);
2. QuerydslRepositorySupport 추상 클래스 사용하기
1. 엔티티 이름, 필드 이름
2. exists...By
3. count...By
4. IsNull
5. querydsl-jpa
6. @Param
7. orderBy()
1.
List<Product> findByPriceGreaterThan(Long price);
2.
List<Product> findByNameOrderByStockDesc(String name);
[스프링 3팀] 10장. 유효성 검사와 예외처리 ~ 11장. 액추에이터 활용하기 (0) | 2025.01.10 |
---|---|
[스프링 3팀] 9장. 연관관계 매핑 (0) | 2025.01.10 |
[스프링 3팀] 7장. 테스트 코드 작성하기 (0) | 2024.12.27 |
[스프링 3팀] 6장. 데이터베이스 연동 (1) | 2024.11.29 |
[스프링 3팀] 5장~6.5장. API 작성과 데이터베이스 연동 (1) | 2024.11.22 |