본문으로 바로가기

[Spring] 실전! Querydsl - 1

category Spring 2021. 9. 9. 14:47

https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지 한번에 해결, 본 강의는 자바 백엔드 개발의 실전 코스를 완성하는 마지막 강의 입니다. 스프링 부트와 JPA 실무 완전 정복 로드맵을 우선 확인해주세요. 로드

www.inflearn.com

※ ~프로젝트 환경설정 & 예제 도메인 모델과 동작확인

-  build.gradle - springdata jpa , spring web , p6spy, h2, lombok, querydsl- jpa, apt 설치 

plugins {
    id 'org.springframework.boot' version '2.5.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    // querydsl 추가
    id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
}

group = 'study'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    //querydsl 추가
    implementation 'com.querydsl:querydsl-jpa'
    implementation 'com.querydsl:querydsl-apt'
    implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.7.1'

    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2:1.4.199'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}


// querydsl 추가 시작
def querydslSrcDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslSrcDir
}

sourceSets {
    main.java.srcDirs querydslSrcDir
}

// 여기부터 gradle 5.0 이후로 추가
configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl{
    options.annotationProcessorPath = configurations.querydsl
}
// querydsl 추가 끝

 

- @Commit 안에 @Rollback(value= false)가 있음 (둘이 같음)

 

※ 기본 문법

ㅁ 시작 - JPQL vs Querydsl

- tasks - other - compileQuerydsl 통해 Q객체 생성

- preparestatement의 parameter binding 방식 사용

 > 문자 더하기 방식이 아니라, sql injection 공격 방어 가능

- 컴파일 시점에 오류를 잡을 수 있다. (문법 오류가 발생할 가능성이 적음)

- 위 두가지는 아주 큰 특징이다. (언어의 한계를 해결하기 위해 노력함 )

 

ㅁ 기본 Q-Type 활용

-  Q객체의 기본 static 생성자 및 static import를 활용하자

 > 깔끔하게 쓸 수 있음

use_sql_comments: true 설정하여 주석통해 JQPL 쿼리 확인

- Qmember member = new Qmember("m1") 은 같은 테이블을 조인해야할 때 alias 구분을 위해 선언해서 사용하자

 

ㅁ 검색 조건 쿼리 & 결과 조회 & 정렬

- .and() 는  , 로 해도됨 (기본 and 적용)

 -> 동적 쿼리 만들때 , 가 좋음

- 정렬문 예시

     * 회원 정렬 순서
     * 1. 회원 나이 내림차순 (desc)
     * 2. 회원 이름 올림차순 (asc)
     * 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
     
	List<Member> result = queryFactory.selectFrom(member)
                .where(member.age.eq(100))
                .orderBy(member.age.desc(), member.username.asc().nullsLast())
                .fetch();

 

ㅁ 페이징

- offset과 limit로 페이징함

- orderby는 페이징의 필수

- 카운트 쿼리를 따로 분리해야할 수도 있음 

 > 컨텐츠 쿼리는 어렵고 카운트는 단순할때

 > ex) where 문을 쓸 경우, join 등으로 복잡할 경우

 

ㅁ 집합

- tuple로 count, sum 등을 가져올 수 있음

 -> 사실 dto로 정리해서 하는걸 실무에서 함

- live templete의 custom 통해 tdd 작성

- groupby 및 having 사용 가능

 

ㅁ 조인

- left join은 left outer join

- 그냥 join 은 inner join

- 세타 조인 : 연관관계가 없는 필드로 조인

 > join on 으로도 가능

 > 카르테시안 곱으로 전체 행 곱하여, 만든 뒤 where 조건에 해당하는 로우만 뽑아냄

 > 성능 최적화는 db가 알아서 해줌

 

ㅁ 조인 - on 절

- ON절을 활용한 조인 ( JPA 2.1부터 지원 )

  1. 조인 대상 필터링

 2. 연관관계 없는 엔티티 외부 조인

 - inner join이면 where 나 on은 똑같음

 > 실무에서는 where 사용

 - left join(left outer join)은 on으로 해야함

- leftjoin(team) 과 leftjoin(member.team, team) 의 차이

 > 후자는 fk 기반으로 체크 후 조인

 > 전자는 관계 없이도 조인함

 

ㅁ 조인 - 페치 조인

- 페치 조인 코드 ( entitymanagerfactory 통해 load 되었는지 확인 )

    @PersistenceUnit
    EntityManagerFactory emf;

    @Test
    public void fetchJoinNo() throws Exception {
        em.flush();
        em.clear();

        Member findMember = queryFactory
                .selectFrom(member)
                .where(member.username.eq("member1"))
                .fetchOne();

        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
        assertThat(loaded).as("패치 조인 미적용").isFalse();
    }

    @Test
    public void fetchJoinUse() throws Exception {
        em.flush();
        em.clear();

        Member findMember = queryFactory
                .selectFrom(member)
                .join(member.team, team).fetchJoin()
                .where(member.username.eq("member1"))
                .fetchOne();

        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
        assertThat(loaded).as("패치 조인 적용").isTrue();
    }

ㅁ 서브 쿼리

- com.querydsl.jpa.jpaexpressions 사용

- 서브쿼리를 위해 q객체 생성 (alias를 통해 구분하기 위해)

- 한계 : JPA JPQL 서브쿼리의 한계점으로 from 서브쿼리(인라인뷰)는 지원하지 않는다. 당연히 Querydsl 도 지원하지 않는다.

  > select 이나 where에서 가능

- 해결방안 

 1. 서브쿼리를 join으로 변경한다.

 2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.

 3. nativeSQL을 사용한다.

- from절 안에 from 절이 과연 좋은 쿼리일까? 고민해봐야함

 -> 여러번 호출해서 어플리케이션에서 처리하는것은?

 

ㅁ Case 문

- case 문으로 구현하기 보다는 다 가져온 후, 어플리케이션에서 처리하는것을 권장

 

ㅁ 상수, 문자 더하기

- Expressions.constant() 로 구현

- 값 합칠 경우, 아래와 같이 사용 (문자 변환할때! 특히 enum 타입)

        List<String> result = queryFactory
                .select(member.username.concat("_").concat(member.age.stringValue()))
                .from(member)
                .where(member.username.eq("member1"))
                .fetch();