[Querydsl] 1. 기본문법

초보개발자·2023년 12월 27일

JPA

목록 보기
9/11
post-thumbnail

💻설정방법

//build.gradle
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"
// 설정 클래스 생성
@RequiredArgsConstructor
@Configuration
public class QueryDslConfiguration {

    @PersistenceContext
    private final EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

  • compileJava 클릭하면 끝

📗JPQL vs Querydsl

//JPQL
//member1을 찾아라.
String qlString ="select m from Member m " +
			"where m.username = :username";
Member findMember = em.createQuery(qlString, Member.class) 
            .setParameter("username", "member1")            
            .getSingleResult();
//Querydsl
//member1을 찾아라.
JPAQueryFactory queryFactory = new JPAQueryFactory(em); 
QMember m = new QMember("m");
Member findMember = queryFactory 
            .select(m)
            .from(m)
            .where(m.username.eq("member1"))//파라미터 바인딩 처리 
            .fetchOne();
  • EntityManager 로 JPAQueryFactory 생성
  • 자바 언어를 사용하기에 컴파일시 오류 확인이 가능

📕JPAQueryFactory를 필드로

private final EntityManager em;
    private final JPAQueryFactory queryFactory;

    public MemberJpaRepository(EntityManager em) {
        this.em = em;
        this.queryFactory = new JPAQueryFactory(em);
    }

동시성 문제는 어떻게 될까?

동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager(em)에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱정하지 않아도 된다.


📘기본 Q-Type 활용

QMember qMember = new QMember("m"); //별칭 직접 지정 
QMember qMember = QMember.member; //기본 인스턴스 사용

기본 인스턴스를 static import와 함께 사용

import static study.querydsl.entity.QMember.*;

Member findMember = queryFactory 
            .select(member) 
            .from(member)
            .where(member.username.eq("member1")) 
            .fetchOne();
  • 같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하자

📙검색 조건 쿼리

Member findMember = queryFactory 
            .selectFrom(member)
            .where(member.username.eq("member1") 
                    .and(member.age.eq(10)))
            //and 대신 파라미터로 삽입 가능
            .where(member.username.eq("member1"),                     		  member.age.eq(10))
            .fetchOne();
  • 검색 조건은 .and() , . or() 를 메서드 체인으로 연결가능
  • select , from 을 selectFrom 으로 합칠 수 있음
member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() //이름이 is not null
member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //between 10, 30
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
member.username.like("member%") //like 검색
member.username.contains("member") // like ‘%member%’ 검색
member.username.startsWith("member") //like ‘member%’ 검색

📃결과 조회

  • fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
  • fetchOne() : 단 건 조회
    결과가 없으면 : null
    결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
  • fetchFirst() : limit(1).fetchOne() //처음 한 건 조회
  • fetchResults() : 페이징,total count //deprecated
  • fetchCount() : count 수 조회 //deprecated

📓정렬

List<Member> result = queryFactory 
            .selectFrom(member) 
            .where(member.age.eq(100))
            .orderBy(member.age.desc(), member.username.asc().nullsLast()) 
            .fetch();
  • desc() , asc() : 일반 정렬
  • nullsLast() , nullsFirst() : null 데이터 순서 부여

📒페이징

List<Member> result = queryFactory 
            .selectFrom(member) 
            .orderBy(member.username.desc()) 
            .offset(1) //0부터 시작(zero index) 
            .limit(2) //최대 2건 조회 
            .fetch();

⌛카운트

Long totalCount = queryFactory
			.select(Wildcard.count) //select count(*)
            .select(member.count()) //select count(member.id) 
            .from(member)
            .fetchOne();

📜집합

List<Tuple> result = queryFactory
            .select(member.count(),
                    member.age.sum(),
                    member.age.avg(),
                    member.age.max(),
                    member.age.min()) 
            .from(member) 
            .fetch();
List<Tuple> result = queryFactory
            .select(team.name, member.age.avg()) 
            .from(member)
            .join(member.team, team) 
            .groupBy(team.name)
            .having(item.price.gt(1000))
            .fetch();

📄기본 조인

QMember member = QMember.member; 
QTeam team = QTeam.team;

List<Member> result = queryFactory 
            .selectFrom(member) 
            .join(member.team, team) 
            .where(team.name.eq("teamA")) 
            .fetch();

📑세타 조인

/**
 * 세타 조인(연관관계가 없는 필드로 조인) 
 * 회원의 이름이 팀 이름과 같은 회원 조회
 * 외부조인 불가 -> on 사용하면 가능
 */
List<Member> result = queryFactory 
            .select(member)
            .from(member, team)            
            .where(member.username.eq(team.name)) 
            .fetch();

😛조인 on절

/**
 * 예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
 * JPQL: SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'teamA'
 * SQL: SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and 
t.name='teamA'
 */
 
List<Tuple> result = queryFactory
            .select(member, team) 
            .from(member)
            .leftJoin(member.team, team).on(team.name.eq("teamA")) 
            .fetch();

내부조인 이면 익숙한 where 절(on과 동일)로 해결하고, 외부조인이 필요한 경우에만 이 기능을 사용하자.


😆연관관계 없는 엔티티 외부 조인

List<Tuple> result = queryFactory 
            .select(member, team) 
            .from(member)
            .leftJoin(team).on(member.username.eq(team.name)) 
            .fetch();

😂페치 조인?

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

🤣서브 쿼리?

//where절 서브쿼리
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory 
            .selectFrom(member) 
            .where(member.age.goe(
					JPAExpressions
                            .select(memberSub.age.avg()) 
                            .from(memberSub) 
            ))
            .fetch();
//select절 서브쿼리
List<Tuple> fetch = queryFactory 
        .select(member.username,
				JPAExpressions
                        .select(memberSub.age.avg()) 
                        .from(memberSub) 
        ).from(member)
        .fetch();
//static import 활용
import static com.querydsl.jpa.JPAExpressions.select; 
List<Member> result = queryFactory
        .selectFrom(member)
        .where(member.age.eq(
				select(memberSub.age.max())
                        .from(memberSub)        
        )) 
        .fetch();

☀️Case 문

//간단한 조건
List<String> result = queryFactory 
        .select(member.age 
                .when(10).then("열살") 
                .when(20).then("스무살") 
                .otherwise("기타")) 
        .from(member)
        .fetch();
//복잡한 조건
List<String> result = queryFactory 
        .select(new CaseBuilder()
                .when(member.age.between(0, 20)).then("0~20살") 
                .when(member.age.between(21, 30)).then("21~30살") 
                .otherwise("기타"))
        .from(member) 
        .fetch();
/**
예를 들어서 다음과 같은 임의의 순서로 회원을 출력하고 싶다면?
1.   0 ~ 30살이 아닌 회원을 가장 먼저 출력
2.   0 ~ 20살 회원 출력
3.   21 ~ 30살 회원 출력
*/
NumberExpression<Integer> rankPath = new CaseBuilder() 
        .when(member.age.between(0, 20)).then(2) 
        .when(member.age.between(21, 30)).then(1) 
        .otherwise(3);
List<Tuple> result = queryFactory
        .select(member.username, member.age, rankPath) 
        .from(member)
        .orderBy(rankPath.desc()) 
        .fetch();
for (Tuple tuple : result) {
	String username = tuple.get(member.username); 
	Integer age = tuple.get(member.age);
	Integer rank = tuple.get(rankPath);
	System.out.println("username = " + username + " age = " + age + " rank = " + rank);
}

⭐상수, 문자 더하기

Tuple result = queryFactory
        .select(member.username, Expressions.constant("A")) 
        .from(member)
        .fetchFirst();
String result = queryFactory
        .select(member.username.concat("_").concat(member.age.stringValue())) 
        .from(member)
        .where(member.username.eq("member1")) 
        .fetchOne();

member.age.stringValue() 부분이 중요한데, 문자가 아닌 다른 타입들은 stringValue() 로 문자로 변환할 수 있다. 이 방법은 ENUM을 처리할 때도 자주 사용한다.


Reference

[인프런] 실전! Querydsl - 김영한

profile
꾸준히 빠르게

0개의 댓글