반응형

 

 

이전 포스팅에서 QueryDSL 사용을 Repository에서 할 수 있도록 설정하는 방법에 대해서 다루어 보았었다. 이제 실제로 자주 사용되는 SQL문을 QueryDSL을 통해 작성해보도록 하자. 

 

JpaRepository는 인터페이스 단에서 기본적인 쿼리를 제공해주지만 동적이고 복잡한 쿼리를 작성이 필요할 때에 QueryDSL 사용을 하는 것이 유리할 수 있다.

 

 

 

아래 포스팅은 Spring Boot 3점대 이상에서 QueryDSL을 설정하고, Repository에서 사용하는 방법을 정리한 글이니 만약 설정방법을 알고 싶다면 참조하면 좋을 것 같다.

 

https://sjh9708.tistory.com/174

 

[Spring Boot/JPA] QueryDSL 설정과 Repository에서의 사용

이번 포스팅에서는 Spring Boot에서 이전에 사용했던 JPQL와 JpaRepository 보다 조금 더 객체지향스럽고 유동적인 동적 쿼리를 작성할 수 있도록 QueryDSL 사용을 위한 설정을 해보도록 하자. QueryDSL에 사

sjh9708.tistory.com

 

 

 

 

 


다뤄볼 내용

 

이번 포스팅에서는 기초적인 SQL에 해당하는 QueryDSL 기능들을 포스팅할 예정들이다. 해당 내용은 아래와 같다. 다른 엔티티와의 연관관계에 대한 서브쿼리 및 Join에 대해서는 다음 포스팅에서 작성해 볼 예정이다.

 

1. 검색(Select) : 리스트 조회, 단일 조회, 컬럼 선택

2. 조건(Where) : 비교 (=), 조건 함수 (gt, lt, between..), 복합 조건 연산 (AND, OR)

3. 정렬 (Order By)

4. 페이지네이션 (Offset, Limit)

5. 집계함수(Aggregation)

6. 그룹화 (Group By, Having)

 

 

 

 

 

 


 

사용할 데이터

 

 

1. Author : Book = 1 : N

Author(저자)는 여러 개의 Book(책)을 가진다.

 

2. Author : Organization = N : 1

Author(저자)는 한곳의 Organization(조직)에 속한다.

 

3. Book : Review = 1 : N

Book(책)은 여러 개의 Review(리뷰)를 가진다.

 

 

 

 

 

 

@Entity
@Table(name = "Organization")
@Getter
@Setter
public class Organization {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orgName;

    @OneToMany(mappedBy = "organization", cascade = CascadeType.ALL)
    private List<Author> authors = new ArrayList<>();

}
@Entity
@Table(name = "Author")
@Getter
@Setter
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private List<Book> book = new ArrayList<>();

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "organization_id")
    private Organization organization;

}
@Entity
@Table(name = "Book")
@Getter
@Setter
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    @OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
    private List<Review> reviews = new ArrayList<>();

}
@Entity
@Table(name = "Review")
@Getter
@Setter
public class Review {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String comment;

    @ManyToOne
    @JoinColumn(name = "book_id")
    private Book book;

}

 

 

 


1. 검색(Select)

 

 

리스트 조회

public List<Book> findBookList() {
    List<Book> result = queryFactory
            .selectFrom(book)
            .fetch();
    return result;
}

 

 

단일 조회

public Book findBookByTitle(String title) {
    Book result = queryFactory.selectFrom(book)
            .where(book.title.eq(title))
            .fetchOne();
    return result;
}

 

  • selectFrom(QType) : 해당 엔티티에서 모든 컬럼 조회
  • fetch() : 리스트를 조회, 데이터가 존재하지 않을 시 빈 리스트를 반환
  • fetchOne() : 단일 조회, 결과가 없으면 Null, 결과가 단일이 아닐 시 Exception 발생
  • fetchFirst() : 단일 조회, 결과가 여러개여도 맨 처음 레코드 반환

 

컬럼 선택하여 조회

public List<String> findBookListTitle() {
    List<String> result = queryFactory.select(book.title)
            .from(book)
            .fetch();
    return result;

 

  • select(QType.Column1, QType.Column2) : 엔티티의 해당 컬럼(들) 조회
  • from(QType) : 조회 대상 엔티티

 


2. 조건(Where)

 

public Book findBookByTitle(String title) {
    Book result = queryFactory.selectFrom(book)
            .where(book.title.eq(title))
            .fetchOne();
    return result;
}

 

  • where(조건) : 해당 조건과 일치하는 레코드(들)을 반환

 

 

 

다양한 조건 함수

 

// 동일 여부
author.name.eq("John"); // 일치
author.name.ne("John"); // 일치X
author.name.isNotNull(); // NullX

// 포함
author.age.in(20, 30, 40); // 포함
author.age.notIn(25, 35, 45); // 미포함

// 문자열
author.name.like("J%"); // LIKE : J로 시작
author.name.startsWith("J"); // J로 시작
author.name.contains("Jo"); // J 포함

// 수 비교
author.age.between(25, 35); // 25 ~ 35
author.age.lt(30); // < 30
author.age.loe(30); // <= 30
author.age.gt(30); // > 30
author.age.goe(30); // >= 30

 

 

복합 조건 연산

 

public List<Author> findAuthorByCondition() {

    List<Author> result = queryFactory.selectFrom(author)
            .where(
                    author.age.notBetween(20, 30)
                            .and(author.age.gt(10))
                            .and(author.age.lt(50))
            )
            .fetch();

    return result;
}
select * from author
where age NOT BETWEEN 20 and 30 
and age > 10 and age < 50;

 

  • and(조건) : AND 복합 조건
  • or(조건) : OR 복합 조건

 

 

 

public List<Author> findAuthorByCondition2() {

    List<Author> result = queryFactory.selectFrom(author)
            .where(
                    (
                            author.age.notBetween(20, 30)
                                    .and(author.age.gt(10))
                                    .and(author.age.lt(50))
                    ).or(
                            author.name.like("%John%")
                    )
            )
            .fetch();


    return result;

}
select * from author
where (age NOT BETWEEN 20 and 30 
and age > 10 and age < 50) or
(name LIKE "%John%");

 

  • () 로 조건들을 묶어 표현식 만들 수 있다.

 

 

 

 

 

연관된 엔티티의 컬럼을 조건으로 사용

 

public List<Book> findBooksByAuthorName(String name) {
    List<Book> result = queryFactory
            .selectFrom(book)
            .where(book.author.name.eq(name))
            .fetch();
    return result;
}

 

  • book.author.name 을 보면 연관된 엔티티의 컬럼을 조건으로 사용할 수 있다. (Book과 Author은 N:1 관계)
  • SQL 쿼리문으로는 아래와 같다.

 

SELECT Book.*
FROM Book
INNER JOIN Author ON Book.author_id = Author.id
WHERE Author.name = 'John Doe';

 

 


3. 정렬 (Order By)

 

public List<Book> findBookListOrderBy() {
    List<Book> result = queryFactory
            .selectFrom(book)
            .orderBy(book.title.desc())
            .fetch();
    return result;
}

 

  • orderBy(QType.Column.(정렬조건)) : 해당 컬럼을 정렬조건에 따라 정렬 (디폴트는 asc)

  • 정렬조건
    • desc() : 내림차순
    • asc() : 올림차순

 

public List<Book> findBookListOrderBy() {
    List<Book> result = queryFactory
            .selectFrom(book)
            .orderBy(book.title.desc().nullsLast())
            .fetch();
    return result;
}

 

 

 

  • nullsLast(), nullsFirst() : Null 데이터에 대한 순서를 부여한다(끝, 시작)

 


4. 페이지네이션 (Offset, Limit)

 

public List<Book> findBookListPagenation(int offset, int limit) {
    List<Book> result = queryFactory
            .selectFrom(book)
            .offset(offset)
            .limit(limit)
            .fetch();
    return result;
}

 

  • offset(long x) : 0부터 시작하는 결과에 대한 오프셋(시작 위치)
  • limit(long x) : 쿼리 결과에 대한 최대치 제한(Limit)

 

 

public List<Book> findBookListPagenation(int offset, int limit) {
    QueryResults<Book> res = queryFactory
            .selectFrom(book)
            .offset(offset)
            .limit(limit)
            .fetchResults();

    System.out.println("Total : " + res.getTotal());
    System.out.println("Limit : " + res.getLimit());
    System.out.println("Offset : " + res.getOffset());
    List<Book> result = res.getResults();
    return result;
}
  • fetchResults : 결과를 가져올 때 페이지네이션 정보를 함께 가져온다. (total, limit, offset) 결과는 getResults()를 호출 시 얻을 수 있다.

 


5. 집계함수(Aggregation)

 

그룹 함수와 함께 주로 사용된다.

public List<Tuple> findAuthorAggregation() {
    List<Tuple> result = queryFactory
            .select(author.count(), author.age.avg())
            .from(author)
            .fetch();
    return result;
}

 

  • count() : 집합의 행 수 계산
  • sum() : 합 계산
  • avg() : 평균 계산
  • max() : 최대 계산
  • min() : 최소 계산

 

 


6. 그룹화 (Group By, Having)

 

 

public List<Tuple> findAuthorGroupByGender() {
    List<Tuple> result = queryFactory
            .select(author.gender, author.count(), author.age.avg())
            .from(author)
            .groupBy(author.gender)
            .fetch();
    return result;
}
SELECT
    author.gender,
    COUNT(author.id),
    AVG(author.age)
FROM
    author
GROUP BY
    author.gender;

 

  • groupBy(컬럼1, 컬럼2..) : 해당 컬럼을 기준으로 그룹화한다.

 

 

public List<Tuple> findAuthorGroupBy() {
    List<Tuple> result = queryFactory
            .select(author.organization.orgName, author.count(), author.age.avg())
            .from(author)
            .groupBy(author.organization.id)
            .having(author.age.avg().gt(10))
            .fetch();
    return result;
}
SELECT
    author_organization.org_name,
    COUNT(author.id),
    AVG(author.age)
FROM
    author
JOIN
    organization AS author_organization ON author.organization_id = author_organization.id
GROUP BY
    author.organization_id
HAVING
    AVG(author.age) > 10;

 

  • having(조건) : 특정 조건을 만족하는 그룹을 필터링한다.
  • 해당 예시는 연관된 엔티티의 컬럼을 기준으로 GroupBy를 수행한 예시이다.

 

 

 


다음 포스팅

 

QueryDSL을 사용하면 연관된 엔티티의 복잡한 Join 및 서브쿼리 수행에 있어서 빛을 발한다.

다음 포스팅에서는 여러 엔티티간의 관계를 다루는 내용을 작성을 해보겠당.

 

 


 

함께 보기

 

1. QueryDSL 설정과 Repository에서의 사용

https://sjh9708.tistory.com/174

 

[Spring Boot/JPA] QueryDSL 설정과 Repository에서의 사용

이번 포스팅에서는 Spring Boot에서 이전에 사용했던 JPQL와 JpaRepository 보다 조금 더 객체지향스럽고 유동적인 동적 쿼리를 작성할 수 있도록 QueryDSL 사용을 위한 설정을 해보도록 하자. QueryDSL에 사

sjh9708.tistory.com

 

2. QueryDSL 기본 문법 

https://sjh9708.tistory.com/175

 

[Spring Boot/JPA] QueryDSL 문법(1) : 기본 검색 (선택, 조건, 정렬, 집계, 그룹화)

이전 포스팅에서 QueryDSL 사용을 Repository에서 할 수 있도록 설정하는 방법에 대해서 다루어 보았었다. 이제 실제로 자주 사용되는 SQL문을 QueryDSL을 통해 작성해보도록 하자. JpaRepository는 인터페이

sjh9708.tistory.com


3. QueryDSL Join 문법

https://sjh9708.tistory.com/178

 

[Spring Boot/JPA] QueryDSL 문법(2) : Join 표현식

이전 포스팅에서 기본적인 QueryDSL의 검색 쿼리 문법에 대해서 살펴보았었다. 이번 포스팅에서는 연관된 다른 릴레이션과의 연산을 수행하는 Join과 Subquery 방법에 대해서 알아보도록 하겠다. 아

sjh9708.tistory.com


4. QueryDSL 서브쿼리와 상수/문자열 조작

https://sjh9708.tistory.com/180

 

[Spring Boot/JPA] QueryDSL 문법(3) : 서브쿼리, 상수/문자열 조작

이전 포스팅에서 기본적인 QueryDSL의 검색 쿼리 문법에 대해서 살펴보았었다. 이번 포스팅에서는 연관된 다른 릴레이션과의 연산을 수행하는 Join과 Subquery 방법에 대해서 알아보도록 하겠다. 아

sjh9708.tistory.com

 

5.  QueryDSL 프로젝션과 Entity > DTO 변환 방법들

https://sjh9708.tistory.com/181

 

[Spring Boot/JPA] Entity > DTO 변환 방법들 및 QueryDSL 프로젝션

이전 포스팅들에서 JPQL, Spring Data JPA Repository, QueryDSL 등을 통해서 데이터를 조회하는 방법들에 대해서 다루어 보았었다. 이번에는 쿼리 결과로 나온 Entity 혹은 Tuple들을 DTO로 매칭하는 방법을 알

sjh9708.tistory.com

 

6.  QueryDSL 동적 쿼리 작성하기

https://sjh9708.tistory.com/182

 

[Spring Boot/JPA] QueryDSL : 동적 쿼리 작성하기

JPA를 사용할 때, QueryDSL을 도입하는 가장 큰 이유는 동적 쿼리 작성에 매우 유연하다는 점이다. 이번 포스팅에서는 동적 쿼리를 작성하는 방법에 대해서 알아보도록 하겠다. 사용할 데이터 1. Auth

sjh9708.tistory.com

 


Reference

 

https://jddng.tistory.com/334#list3

 

Querydsl - Querydsl 기본 문법

Querydsl 기본 문법 Querydsl 사용 방법 Q-Type 검색 조건 쿼리 결과 조회 정렬 페이징 집합 조인 - 기본 조인 조인 - on절 조인 - 페치 조인 서브 쿼리 Case 문 상수, 문자 더하기 기본 문법을 테스트하기

jddng.tistory.com

 

 

 

반응형

BELATED ARTICLES

more