일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- SQL
- 디자인 패턴
- spring boot
- 이것이 자바다
- 알고리즘
- 포트폴리오
- 데이터 베이스
- Join
- LV0
- 연습문제
- S3
- LV1
- 코테
- LV03
- LV01
- 배열
- docker
- CoffiesVol.02
- 네트워크
- 프로그래머스
- Til
- LV02
- 일정관리프로젝트
- LV.02
- GIT
- Lv.0
- Redis
- mysql
- JPA
- Java
- Today
- Total
코드 저장소.
JPQL에서 QueryDSL을 활용한 동적쿼리 적용 본문
목차
1.QueryDSL?
2.JPQL과의 차이점?
3.프로젝트의 적용
1.QueryDSL?
QueryDSL은 자바 기반의 동적 쿼리를 작성하는 데 사용되는 라이브러리입니다. 주로 JPA(Java Persistence API)와 함께 사용되며, 데이터베이스에 대한 쿼리를 자바 코드로 작성할 수 있도록 도와주는 라이브러리 입니다.
2.JPQL과의 차이점?
QueryDSL과 JPQL(JPA Query Language)은 둘 다 자바 표준인 JPA(Java Persistence API)를 사용하여 데이터베이스 쿼리를 작성하는 데에 사용되는 라이브러리입니다. 하지만 두 라이브러리 간에는 몇 가지 차이가 있습니다.
2-1.문법 및 표현 방법
JPQL: 문자열 기반의 쿼리 언어로, SQL과 유사하지만 엔터티와 필드를 대상으로 쿼리를 작성합니다. JPQL 쿼리는 문자열로 작성되기 때문에 컴파일 타임에 오류를 발견하기 어렵습니다.
QueryDSL: 자바 언어를 사용하여 쿼리를 작성하는 데에 중점을 둡니다. 코드로 작성되므로 컴파일 타임에 오류를 쉽게 찾을 수 있습니다. 또한 IDE 지원을 통해 코드 자동 완성 및 리팩토링 기능을 활용할 수 있습니다.
2-2.타입 안전성
JPQL: 문자열로 작성되기 때문에 컴파일러가 쿼리에 대한 타입을 확인할 수 없습니다. 따라서 실행 시점에 오류가 발생할 가능성이 있습니다.
QueryDSL: 자바 코드로 작성되기 때문에 타입 안전성을 보장합니다. IDE에서 코드 자동 완성 및 타입 체크를 지원하여 오류를 줄일 수 있습니다.
2-3.가독성
JPQL: 문자열로 작성되기 때문에 복잡한 쿼리의 경우 가독성이 떨어질 수 있습니다. 특히 동적인 쿼리를 작성할 때는 문자열 조합이 필요합니다.
QueryDSL: 자바 코드로 작성되므로 가독성이 향상될 수 있습니다. 코드의 구조를 활용하여 복잡한 쿼리도 보다 명확하게 작성할 수 있습니다.
2-4.코드 재사용 및 리팩토링
JPQL: 문자열 기반으로 코드 리팩토링이 어렵습니다. 코드 중복이 발생할 가능성이 있습니다.
QueryDSL: 자바 코드로 작성되어 코드 리팩토링이 용이합니다. 코드를 재사용하고 모듈화할 수 있어 유지보수가 편리합니다.
3.프로젝트의 적용
3-1. query dsl을 적용하기 위해서 build.gradle에 다음과 같이 작성을 합니다.
//querydsl
implementation "com.querydsl:querydsl-jpa"
implementation "com.querydsl:querydsl-core"
implementation "com.querydsl:querydsl-collections"
......
// Querydsl 설정부
def generated = 'src/main/generated'
// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}
// java source set 에 querydsl QClass 위치 추가
sourceSets {
main.java.srcDirs += [ generated ]
}
// gradle clean 시에 QClass 디렉토리 삭제
clean {
delete file(generated)
}
tasks.named('test') {
useJUnitPlatform()
}
targetCompatibility = JavaVersion.VERSION_14
3-2.builde.gradle 내에서 query dsl에 관한 설정을 적어 준 뒤 build를 합니다. build가 끝나면 옆의 사진과 같이 Q가 붙어있는 클래스가 생성 되는 것을 볼 수가 있습니다.
3-3. QueryDsl에 관련된 설정 클래스를 작성해야 합니다.
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
3-4.Repository의 구조를 변경을 합니다.
- 쿼리 메소드나 @Query 등으로 처리할 수 없는 기능은 별도의 인터페이스로 설계
- 별도의 인터페이스에 대한 구현 클래스를 작성한다
- 구현 클래스에 인터페이스의 기능을 Q도메인 클래스와 JPQLQuery를 이용해서 구현한다.
3-5.QueryDsl을 적용
BoardCustomReposiotry에 원하는 기능의 메소드를 작성을 합니다.
public interface BoardCustomRepository {
//게시글 검색(카테고리 ,검색 ,정렬)
Page<BoardDto.BoardResponseDto> findByAllSearch(String searchVal, SearchType searchType , Pageable pageable);
}
그 다음은 JPAQueryFactory를 이용해서 본격적으로 QueryDSL코드를 작성해보겠습니다.
@Log4j2
public class BoardCustomRepositoryImpl implements BoardCustomRepository{
private final JPAQueryFactory jpaQueryFactory;
QBoard qBoard;
QMember qMember;
QLike qLike;
QCategory qCategory;
QBoardHashTag qBoardHashTag;
QHashTag qHashTag;
//생성자 주입
public BoardCustomRepositoryImpl(EntityManager em) {
this.jpaQueryFactory = new JPAQueryFactory(em);
qBoard = QBoard.board;
qMember = QMember.member;
qLike = QLike.like;
qCategory = QCategory.category;
qHashTag = QHashTag.hashTag;
qBoardHashTag = QBoardHashTag.boardHashTag;
}
.......
@Override
public Page<BoardDto.BoardResponseDto> findByAllSearch(String searchVal, SearchType searchType, Pageable pageable) {
JPQLQuery<BoardDto.BoardResponseDto>list = jpaQueryFactory
.select(Projections.constructor(BoardDto.BoardResponseDto.class,qBoard))
.from(qBoard)
.leftJoin(qBoard.writer,qMember).fetchJoin()
.leftJoin(qBoard.category,qCategory)
.leftJoin(qBoard.likes,qLike)
.leftJoin(qBoard.hashtags,qBoardHashTag)
.distinct();
JPQLQuery<BoardDto.BoardResponseDto>middleQuery = switch (searchType){
//제목
case TITLE -> list.where(titleCt(searchVal));
//작성자
case AUTHOR -> list.where(authorCt(searchVal));
//내용
case CONTENTS -> list.where(contentCt(searchVal));
//ALL
default ->list.where(titleCt(searchVal).or(authorCt(searchVal)).or(contentCt(searchVal)));
};
return PageableExecutionUtils
.getPage(middleQuery
.orderBy(getAllOrderSpecifiers(pageable.getSort()).toArray(OrderSpecifier[]::new))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch(),
pageable,
middleQuery::fetchCount);
}
}
- JPQLQuery를 사용해서 게시글을 조회하는 쿼리를 작성을 합니다.
- 중간 쿼리로 게시글을 검색을 할 때 검색어에 따른 쿼리를 switch문을 사용해서 작성을 합니다.
- 마지막으로는 PageableExecutionUtils을 사용해서 페이징 처리와 정렬을 합니다.
3-6. 테스트 코드
마지막으로 검색기능이 잘 작동이 되는지를 확인하기 위해서 테스트 코드를 작성해 보았습니다.
@Test
@DisplayName("게시글 검색")
public void boardSearchTest(){
Pageable pageable = Pageable.ofSize(10);
String keyword = "well4149";
Page<BoardDto.BoardResponseDto>list = boardRepository.findByAllSearch(keyword, SearchType.toSearch("boardAuthor"),pageable);
System.out.println(list.toList());
assertThat(list);
System.out.println("size::"+list.stream().count());
}
테스트를 이용해보면 위와 같은 쿼리를 볼 수 있고 테스트가 통과 되는것을 볼 수 있습니다.
Hibernate: select
distinct
board0_.board_id as board_id1_0_0_,
member1_.id as id1_6_1_,
board0_.created_at as created_2_0_0_,
board0_.board_author as board_au3_0_0_,
board0_.board_contents as board_co4_0_0_,
board0_.board_title as board_ti5_0_0_,
board0_.category_id as category9_0_0_,
board0_.liked as liked6_0_0_,
board0_.board_pw as board_pw7_0_0_,
board0_.read_count as read_cou8_0_0_,
board0_.useridx as useridx10_0_0_,
member1_.created_at as created_2_6_1_,
member1_.membername as memberna3_6_1_,
member1_.password as password4_6_1_,
member1_.role as role5_6_1_,
member1_.useremail as useremai6_6_1_,
member1_.userid as userid7_6_1_
from
board board0_
left outer join
member member1_
on board0_.useridx=member1_.id
left outer join
category category2_
on board0_.category_id=category2_.category_id
left outer join
likes likes3_
on board0_.board_id=likes3_.board_id
left outer join
boardhashtag hashtags4_
on board0_.board_id=hashtags4_.board_id
where
lower(board0_.board_author) like ? escape '!' limit ?
Hibernate: select
hashtags0_.board_id as board_id1_1_2_,
hashtags0_.hashtag_id as hashtag_2_1_2_,
hashtags0_.board_id as board_id1_1_1_,
hashtags0_.hashtag_id as hashtag_2_1_1_,
hashtag1_.hashtag_id as hashtag_1_4_0_,
hashtag1_.created_at as created_2_4_0_,
hashtag1_.hashtag_name as hashtag_3_4_0_
from
boardhashtag hashtags0_
inner join
hashtag hashtag1_
on
hashtags0_.hashtag_id=hashtag1_.hashtag_id
where
hashtags0_.board_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: select
category0_.category_id as category1_2_0_,
category0_.name as name2_2_0_,
category0_.parent_id as parent_i3_2_0_
from
category category0_
where
category0_.category_id=?
Hibernate: select
count(distinct board0_.board_id) as col_0_0_
from
board board0_
left outer join
member member1_
on
board0_.useridx=member1_.id
left outer join
category category2_
on
board0_.category_id=category2_.category_id
left outer join
likes likes3_
on
board0_.board_id=likes3_.board_id
left outer join
boardhashtag hashtags4_
on
board0_.board_id=hashtags4_.board_id
where
lower(board0_.board_author) like ? escape '!'
'포폴 > JPABlog' 카테고리의 다른 글
AOP를 활용해서 공통로직에 적용되는 코드 리팩토링 (0) | 2024.06.01 |
---|---|
게시글 조회수에서 발생한 동시성 제어 (0) | 2024.05.31 |
Redis Cache로 조회 성능 향상시키기. (0) | 2024.05.31 |
Jwt를 활용한 로그인을 구현하기 (0) | 2024.05.31 |
MyBatis에서 JPA로 변경 (0) | 2023.09.17 |