티스토리 뷰
아래 내용은 QueryDSL - 김영한 강의 영상을 보고 실무에서 필요할 때
찾아보려고 정리한 내용입니다.
1. QueryDSL 설정 |
2. QueryRepositorySupport & Paging |
3. QueryDSL WEB 지원 |
4. QClass 인식 실패시 |
1. QueryDSL 설정
- Gradle
- Java 17
- Spring Boot 3.1.5
- QueryDSL 5.0.0
- DB H2
- Gradle 내용 가장 아래 QueryDSL 빌드 옵션
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
}
group = 'com.jpa'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.1'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Spring boot 3.x 이상 QueryDsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
tasks.named('test') {
useJUnitPlatform()
}
// 개발 툴 그리고 툴 버전이 다른 경우(Intellij or Eclipse or the rest) 구성이 달라질 수 있다고 합니다
// 선택 사항 QueryDsl 빌드 옵션
def querydslDir = "$buildDir/generated/querydsl"
sourceSets {
main.java.srcDirs += [ querydslDir ]
}
tasks.withType(JavaCompile).configureEach {
options.generatedSourceOutputDirectory.set(file(querydslDir))
}
clean.doLast {
file(querydslDir).deleteDir()
}
위 build.gradle 파일에서 주석으로 QueryDSL 관련된 부분을 추가하시고
필요한 엔티티를 생성한 후 gradle - Tasks - other - compileJava 클릭하시면 위 QueryDSL 빌드 옵션 경로에
Q클래스 파일이 생성되고 QueryDSL 을 사용할 수 있게 됩니다
그리고 build 폴더 안에 생성되기 때문에 .gitignore 파일에 의해 별다른 설정 없이 커밋 제외대상이 됩니다
2. QueryDSL RepositorySupport 한계 커스텀으로 극복하기, Paging
우선 QuerydslRepositorySupport의 한계로는
조인 불가능, 시작이 from절 부터 시작하여 마지막에 select 작성, 정렬시 이상한 값으로 바뀌는 경우... 등
여러가지 문제가 있습니다
장점으로는 QueryDSL을 사용하기 위해 Repository, 또는 Paging 기능 마다 아래와 같은 코드가 반복적으로 들어갑니다
이를 작성하지 않아도 됩니다
1. QueryDSL 사용하기 위한 factory 생성
private final JPAQueryFactory queryFactory;
public MemberRepositoryImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}
2. Paging 이 필요한 코드마다 동일하게 들어가는 값
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
사용 방법은 아래 코드로 확인 가능하며 총 4개의 파일로 구성되어 있습니다
- MemberRepository(Spring Data JPA 인터페이스)
- MemberRepositoryCustom(Spring Data JPA 인터페이스에서 제공해주는 기능 외에 필요한 쿼리메서드 정의)
- MemberRepositoryImpl(MemberRepositoryCustom에 작성한 쿼리메서드 구현)
- QuerydslRepositorySupportCustom(MemberRepositoryImpl 에 작성된 코드 더 심플하게 만들어주는 역할)
//== MemberRepository ==//
public interface MemberRepository extends
JpaRepository<Member, Long>, MemberRepositoryCustom {
}
//== MemberRepositoryCustom ==//
public interface MemberRepositoryCustom {
List<Member> findMemberCustom();
Page<MemberTeamDto> findMemberCustom(MemberSearchCondition condition, Pageable pageable);
}
//== MemberRepositoryImpl ==//
import static com.jpa.jpaboilerplate.entity.QMember.member;
import static com.jpa.jpaboilerplate.entity.QTeam.team;
public class MemberRepositoryImpl extends QuerydslRepositorySupportCustom implements MemberRepositoryCustom {
public MemberRepositoryImpl() {
super(Member.class);
}
public Page<MemberTeamDto> searchMemberCustom(MemberSearchCondition condition, Pageable pageable) {
return applyPagination(pageable, query -> query
.selectFrom(member)
.leftJoin(member.team, team)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
));
}
public Page<MemberTeamDto> searchMemberCustomAndCount(MemberSearchCondition condition, Pageable pageable) {
return applyPagination(
pageable,
// content(Data)
query -> query
.selectFrom(member)
.leftJoin(member.team, team)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
),
// count
countQuery -> countQuery
.select(member.id)
.from(member)
.leftJoin(member.team, team)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
)
);
}
private BooleanExpression usernameEq(String username) {
return username != null ? member.username.eq(username) : null;
}
private BooleanExpression teamNameEq(String teamName) {
return teamName != null ? team.name.eq(teamName) : null;
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe != null ? member.age.loe(ageLoe) : null;
}
}
//== QuerydslRepositorySupportCustom ==//
@Repository
public abstract class QuerydslRepositorySupportCustom {
private final Class domainClass;
private Querydsl querydsl;
private EntityManager entityManager;
private JPAQueryFactory queryFactory;
public QuerydslRepositorySupportCustom(Class<?> domainClass) {
Assert.notNull(domainClass, "Domain class must not be null");
this.domainClass = domainClass;
}
@Autowired
public void setEntityManager(EntityManager entityManager) {
Assert.notNull(entityManager, "EntityManager class must not be null");
JpaEntityInformation entityInformation = JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
EntityPath path = resolver.createPath(entityInformation.getJavaType());
this.entityManager = entityManager;
this.querydsl = new Querydsl(entityManager, new PathBuilder<>(path.getType(), path.getMetadata()));
this.queryFactory = new JPAQueryFactory(entityManager);
}
@PostConstruct
public void validate() {
Assert.notNull(entityManager, "EntityManager class must not be null");
Assert.notNull(querydsl, "Querydsl class must not be null");
Assert.notNull(queryFactory, "JPAQueryFactory class must not be null");
}
protected JPAQueryFactory getQueryFactory() {
return queryFactory;
}
protected Querydsl getQuerydsl() {
return querydsl;
}
protected EntityManager getEntityManager() {
return entityManager;
}
protected <T> JPAQuery<T> select(Expression<T> expr) {
return getQueryFactory().select(expr);
}
protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
return getQueryFactory().selectFrom(from);
}
protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery) {
JPAQuery jpaQuery = contentQuery.apply(getQueryFactory());
List<T> content = getQuerydsl().applyPagination(pageable, jpaQuery).fetch();
return PageableExecutionUtils.getPage(content, pageable, jpaQuery::fetchCount);
}
// count 최적화 메서드
protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory, JPAQuery> countQuery) {
JPAQuery jpaQuery = contentQuery.apply(getQueryFactory());
List<T> content = getQuerydsl().applyPagination(pageable, jpaQuery).fetch();
JPAQuery<Long> count = countQuery.apply(getQueryFactory());
return PageableExecutionUtils.getPage(content, pageable, count::fetchOne);
}
}
3. QueryDSL WEB 지원
컨트롤러에서 요청 파라미터에 Predicate 를 받아서 파라미터에 조건을 바로 세팅하는 방법입니다
이 기능은 단순한 요청에 특화되어 있고 실무 사용에는 한계점이 크기 때문에 사용을 권장하지 않는다고 합니다.
4. QClass 인식 실패시 아래 링크에서 해결 방법들 참조
'JPA > QueryDSL' 카테고리의 다른 글
QueryDSL 문법 설명 및 사용 예제 정리 (0) | 2023.11.13 |
---|
- Total
- Today
- Yesterday
- CodeBuild
- 시스템 환경변수
- AWS 로드밸런서 SSL 등록
- Spring Boot 3.x
- ELK
- AWS CodePipeline
- CodePipeline
- AWS 자동 배포
- Certbot
- Spring Data JPA
- certonly
- AWS MSK
- jasypt
- 네임 서버 변경
- aws codecommit
- aws codedeploy
- Spring boot
- letsencrypt
- 도메인 내부 테스트
- properties 암호화
- REST API
- QueryDSL 사용 방법
- codedeploy
- querydsl
- logstash
- JPA 벌크성 수정 쿼리
- AWS Opensearch
- aws codebuild
- 후이즈에서 AWS Route 53
- kibana
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |