Querydsl에 들어가기 전...
먼저
✔️ JPQL이란?
JPQL(Java Persistence Query Language)은 엔티티 객체를 조회하는 객체지향 쿼리다.
→ JPQL을 한마디로 정의하면 객체 지향 SQL.
- @Query 어노테이션 이용.
- 문법은 SQL과 비슷하고, SQL이 제공하는 기능을 유사하게 지원한다.
- JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공한다.
- JPQL은 SQL을 추상화하기 때문에 특정 데이터베이스에 의존하지 않는다.
- 데이터베이스 방언(Dialect)만 변경하면, JPQL을 수정하지 않아도 자연스럽게 데이터베이스를 변경할 수 있다.
- SQL보다 간결하다.
- SQL과 문법 유사, SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원.
- JPQL은 엔티티 객체를 대상으로 쿼리
- SQL은 데이터베이스 테이블을 대상으로 쿼리
- 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
- SQL, JPQL은 문자, Type-chek 불가능. 따라서 해당 로직 실행 전까지 작동여부 확인 불가하다.
✔️ JPQL 예를 들어보자
@Query 어노테이션 이용.
게시물 데이터 조회 예제.
- 게시물 데이터 조회 예제
- 조건 : 게시물 상세 설명을 파라미터로 받아 해당 내용을 게시물 상세 설명에 포함하고 있는 데이터를 조회, 정렬순서는 가격이 높은 순.
@Query("select r from Recipe r where r.recipeDetail like %:recipeDetail% order by r.price desc")
List<Recipe> findByRecipeDetail(@Param("recipeDetail") String recipeDetail);
→ @Query 어노테이션 안에 JPQL로 작성한 쿼리문을 넣어준다.
→ from 뒤에는 엔티티 클래스로 작성한 Recipe를 지정해준다.
→ Recipe로부터 데이터를 select 하겠다는 뜻이다.
→ 파라미터에 @Param 어노테이션을 이용하여, 파라미터로 넘어온 값을 JPQL에 들어갈 변수로 지정해줄 수 있다.
→ 현재는 recipeDetail 변수를 "like % %" 사이에 ":recipeDetail"로 값이 들어가도록 한 것이다.
💡 만약, 기존의 데이터베이스에서 사용하던 쿼리를 그대로 사용해야 할 때는?
→ @Query의 nativeQuery 속성을 사용하면, 기존 쿼리를 그대로 사용할 수 있다.
🚨 하지만 특정 데이터베이스에 종속되는 쿼리문을 사용하기 때문에 데이터베이스에 대해 독립적이라는 장점을 잃어버린다.
✔️ QueryDSL이란?
JPQL을 코드로 작성할 수 있도록 도와주는 빌더 API이다.
- 문자가 아닌 자바코드로 JPQL을 작성할 수 있다.
- JPQL 빌더 역할.
- 소스코드로 SQL문을 문자열이 아닌 코드로 작성하기 때문에, 컴파일 시점에 문법 오류를 찾을 수 있다. (소스 작성 시 오타가 발생하면 개발자에게 오타가 있음을 바로 알려줌. 빨간줄)
- 고정된 SQL문이 아닌, 조건에 맞게 동적으로 쿼리를 생성할 수 있다. (JPQL은 문자를 계속 더해야 하기 때문에 작성이 힘듦)
- 자동 완성 등 IDE의 도움을 받을 수 있다.
- 비슷한 쿼리를 재사용할 수 있다.
- 제약 조건 조립 및 가독성을 향상시킬 수 있다.
- 단순하고 쉬움 (난 어려움)
- 실무 사용 권장
💡 Querydsl을 통해서 쿼리를 생성할 때, Qdomain 객체를 사용한다.
Ex) QMember : Member 엔티티 클래스를 기반으로 생성한 QueryDSL 쿼리 전용 클래스이다.
✔️ JPAQuery 데이터 반환 메소드
메소드 | 기능 |
List<T> fetch() | 조회 결과 리스트 반환 |
T fetchOne | 조회 대상이 1건인 경우, 제네릭으로 지정한 타입 반환 |
T fetchFirsh() | 조회 대상 중 1건만 반환 |
Long fetchCount() | 조회 대상 개수 반환 |
QueryResult<T> fetchResults() | 조회한 리스트와 전체 개수를 포함한 QueryResults 반환 |
✔️ QueryDSL 간단 예제
1. QueryDSL - 사용
// JPQL
// select m form Member m where m.age > 18
JPAFactoryQuery query = new JPAQueryFactory(em);
QMember m = QMember.member;
List<Member> list = query
.selectFrom(m)
.where(m.age.gt(18))
.orderBy(m.name.desc())
.fetch();
2. QueryDSL - 동적 쿼리
String name = "member";
int age = 9;
QMember m = QMember.member;
BooleanBuilder builder = new BooleanBuilder();
if(name != null) {
builder.and(m.name.contains(name));
}
if(age != 0) {
builder.and(m.age.gt(age));
}
List<Member> list = query
.selectFrom(m)
.where(builder)
.fetch();
3. QueryDSL - 조인
JPAQueryFactory query = new JPAQueryFactory(em);
QMember m = QMember.member;
QTeam t = QTeam.team;
List<Member> list = query
.selectFrom(m)
.join(m.team, t)
.where(t.name.eq("teamA"))
.fetch();
4. QueryDSL - 이것은 자바다!
- 제약조건 조립 가능
- 가독성, 재사용
(화면 캡쳐한 거라 화질 구지ㅋ;;)
5. 걍 예제
/**
* 검색어가 null이 아니면, 게시물명에 해당 검색어가 포함되는 게시물을 조회하는 조건 반환
*
* BooleanExpression ; null 반환 시 자동으로 조건절에서 제거됨
* 단, 모든 조건이 null인 경우 장애 발생
* */
private BooleanExpression recipeNameLike(String searchQuery) {
return StringUtils.isEmpty(searchQuery) ? null : QRecipe.recipe.recipeName.like("%" + searchQuery + "%");
}
@Override
public Page<MainItemDto> getMainRecipePage(RecipeSearchDto recipeSearchDto, Pageable pageable) {
QRecipe recipe = QRecipe.recipe; // Querydsl을 통해 쿼리를 생성하기 위해, 플러그인을 통해 자동으로 생성된 QRecipe 객체를 이용함.
QRecipeImg recipeImg = QRecipeImg.recipeImg;
List<MainItemDto> content = queryFactory
.select(
new QMainItemDto( // QMainItemDto의 생성자에 반환할 값 넣어줌. @QueryProjection 덕분에 엔티티 조회 후 DTO 변환 과정 없이 DTO로 바로 조회 가능.
recipe.id,
recipe.recipeName,
recipe.recipeDetail,
recipeImg.imgUrl,
recipe.price)
)
.from(recipeImg)
.join(recipeImg.recipe, recipe) // RecipeImg와 Recipe를 내부 조인
.where(recipeImg.repimgYn.eq("Y")) // 대표 이미지만 가져옴
.where(recipeNameLike(recipeSearchDto.getSearchQuery()))
.orderBy(recipe.id.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch(); // JPAQuery 메소드 중 하나인 fetch를 이용해서 쿼리 결과를 리스트로 반환한다. fetch() 메소드 실행 시점에 쿼리문이 실행된다.
long total = queryFactory
.select(Wildcard.count) // select count(*)
.from(recipeImg)
.join(recipeImg.recipe, recipe)
.where(recipeImg.repimgYn.eq("Y"))
.where(recipeNameLike(recipeSearchDto.getSearchQuery()))
.fetchOne()
;
// Page<User> page = new PageImpl<>(users.subList(start, end), pageable, users.size());
// content의 타입을 바꿔준 list, pageable, 총 데이터 개수
return new PageImpl<>(content, pageable, total);
}
아 어렵네;;
계속 진행 중...
계속 추가 예정.
출처
- https://tecoble.techcourse.co.kr/post/2021-08-08-basic-querydsl/
- https://ict-nroo.tistory.com/117
- 김영한 개발자님 [자바 ORM 표준 JPA 프로그래밍]
- 김영한 개발자님 강의
'Spring > JPA' 카테고리의 다른 글
[JPA] OSIV란? (6) | 2023.02.07 |
---|---|
[JPA] MyBatis와 JPA, 도대체 뭐가 다를까? (0) | 2023.02.06 |
스프링 데이터 JPA - (2) (0) | 2022.12.20 |
스프링 데이터 JPA - (1) (0) | 2022.12.19 |
JPA 활용 2 - API 개발과 성능 최적화 (0) | 2022.12.19 |