허원철의 개발 블로그

Spring Boot - QueryDSL 본문

web

Spring Boot - QueryDSL

허원철 2017. 1. 25. 23:51

이번 글은 Spring Boot에서 QueryDSL를 사용한 예제에 대한 글입니다.


우리나라에서는 흔히 사용하는 ORM은 ibatis, mybatis라고 볼 수 있습니다. 외국에서는 옛날부터 JPA를 주로 사용합니다. 그런데 요즘 우리나라에서도 (Spring Boot에서 jpa 연동이 간단해서 그런가..?) 예전보다 사용율이 높아진 것 같습니다. 


<출처 : http://www.slideshare.net/ZeroTurnaround/java-tools-and-technologies-landscape-for-2014-image-gallery >



JPA만으로는 제한적인 것들이 많아서 JPA Criteria나 QueryDSL를 사용하여 디테일한 표현을 가능하게 합니다. 몇 달 전에 아주아주 간단하게 JPA를 접해봤습니다(너무 미흡한 점이 많았습니다. 깊이 반성합니다..ㅠ). 그래서 이번에는 QueryDSL를 이용한 예제를 하나씩 살펴보려 합니다. 


QueryDSL은 jpa를 쉽게 쓰기 위한 라이브러리로, 도메인 객체가 Q도메인 형태로 만들어지는데 이를 이용하여 보다 편하게 쿼리를 작성할 수 있게 도와줍니다. 물론, 객체간의 관계 설정은 확실하게 해주어야 합니다. 또한, Q도메인 클래스를 생성하기 위한 설정도 필요합니다. (레퍼런스 문서에서는 maven으로만 설명이 되어 있어, 다른 블로거님의 설정을 참고 했습니다.)


※ QueryDSL은 한글 레퍼런스 문서가 존재하므로, 쉽게 보실 수 있습니다. 다만 업데이트가 조금 느려서 영문과 다를 수 있습니다.


Spring Boot Version : 1.4.3.RELEASE

Hibernate    Version : 5.0.11.Final

QueryDSL    Version : 4.1.4



- BASE DATA


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Grade firstGrade = new Grade("일학년");
Grade secondGrade = new Grade("이학년");
Grade thirdGrade = new Grade("삼학년");
 
Student wonchul = new Student("wonchul"173.8);
Student naeun = new Student("naeun"165.2);
Student tistory = new Student("tistory"160.0);
 
firstGrade.getStudents().add(wonchul);
secondGrade.getStudents().add(naeun);
thirdGrade.getStudents().add(tistory);
 
gradeRepository.save(firstGrade);
gradeRepository.save(secondGrade);
gradeRepository.save(thirdGrade);
 
studentRepository.save(wonchul);
studentRepository.save(naeun);
studentRepository.save(tistory);
 
entityManager.flush();
cs



1. 기본

1
SELECT * FROM STUDENT WHERE STUDENT_NAME = 'wonchul'
cs


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
32
33
34
35
36
37
1.
@Override
public Predicate equalName(String name) {
    Path<Student> path            = Expressions.path(Student.class"student");
    Path<String>  studentName    = Expressions.path(String.class, path, "name");
    Expression<String> equalName= Expressions.constant(name);
    return Expressions.predicate(Ops.EQ, studentName, equalName);
}
 
studentRepository.findStudentByName("wonchul")
                            .forEach(System.out::println);
 
2.
@Override
public List<Student> findStudentByName(String name) {
    QStudent student = QStudent.student;
    return queryFactory
            .selectFrom(student)
            .where(student.name.eq(name))
            .fetch();
}
 
studentRepository.findAll(studentRepository.equalName("wonchul"))
                            .forEach(System.out::println);
 
3.
@Override
public List<Student> findStudentByNameExtension(String name) {
    PathBuilder<Student> studentPath = new PathBuilder<>(Student.class"student");
    
    return queryFactory.selectFrom(studentPath)
            .where(studentPath.get("name").eq(name))
            .fetch();
}
 
studentRepository.findStudentByNameExtension("wonchul")
                            .forEach(System.out::println);
cs


1) 가장 간단하게 사용할 수 있는 방법입니다.

2) Q클래스를 못 만드는 경우, Predicate를 만들어서 사용할 수 있는 방법입니다.

3) 2)과 동일하게 Q클래스를 못 만드는 경우, 쿼리를 작성 방법입니다.



2. 조건절

1
SELECT * FROM STUDENT WHERE STUDENT_GRADE = 1 AND STUDENT_HEIGHT >= 165
cs


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
@Override
public List<Student> findStudentByNameAndHeight(String name, Double height) {
    QStudent student = QStudent.student;
    
    BooleanBuilder builder = new BooleanBuilder();
    builder.and(student.name.eq(name));
    builder.and(student.height.goe(height));
 
1.    
    return queryFactory
            .selectFrom(student)
            .where(builder)
            .fetch();
 
2.
    return queryFactory
            .selectFrom(student)
            .where(student.name.eq(name), student.height.goe(height))
            .fetch();
 
3.
    return queryFactory
            .selectFrom(student)
            .where(student.name.eq(name).and(student.height.goe(height)))
            
cs


1) 동적으로 조건절을 추가하고 싶은 경우 사용하는 방법입니다.

2) 3) 동일한 방법이나 3)이 보기에 더 명확하지 않을까 싶습니다.



3. 정렬

1
SELECT * FROM STUDENT ORDER BY STUDENT_NAME
cs


1
2
3
4
5
6
7
8
@Override
public List<Student> findStudentOrderByName() {
    QStudent student = QStudent.student;
    return queryFactory
            .selectFrom(student)
            .orderBy(student.name.asc())
            .fetch();
}
cs


- asc(), desc() 등을 추가할 수 있습니다.



4. 그룹

1
2
3
4
5
SELECT       GRADE.GRADE_NUM
FROM         GRADE
INNER JOIN   STUDENT
ON           GRADE.GRADE_NUM = STUDENT.GRADE_NUM
GROOUP BY    GRADE.GRADE_NUM
cs


1
2
3
4
5
6
7
8
9
10
11
@Override
public List<Integer> findStudentGroupingByGradeNum() {
    QStudent student = QStudent.student;
    QGrade grade = QGrade.grade;
    return queryFactory
            .select(grade.gradeNum)
            .from(grade)
            .join(grade.students, student)
            .groupBy(grade.gradeNum)
            .fetch();
}
cs


- Group에 넣고 싶은 컬럼을 나열합니다.

- 별개로, 별개의 컬림을 보이고 싶다면 select(보이고 싶은 컬럼, ...) 사용 가능 합니다.



5. CASE

1
2
3
4
5
6
7
8
9
10
SELECT      CASE
            WHEN GRADE0_.GRADE_NUM>? THEN '고학년'
            ELSE '저학년'
            END,
            STUDENT.STUDENT_ID,
            STUDENT.STUDENT_NAME,
            STUDENT.STUDENT_HEIGHT
FROM        GRADE
INNER JOIN  STUDENT
ON          GRADE.GRADE_NUM=STUDENT.GRADE_NUM
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public List<Tuple> findCaseStudentAll_Tuple() {
    QStudent student = QStudent.student;
    QGrade grade = QGrade.grade;
    Expression<String> cases = new CaseBuilder()
            .when(grade.gradeNum.gt(3)).then("고학년")
            .otherwise("저학년");
 
        return queryFactory
            .select(cases, student.id, student.name, student.height)
            .from(grade)
            .join(grade.students, student)
            .fetch();
}
cs


- CaseBuilder로 case를 작성 할 수 있습니다.

- Tuple를 이용해서 도메인이 아닌 컬럼들을 뽑을 수 있습니다. 또한 Projections를 이용해서도 가능 합니다.



6. 조인

1
2
3
4
5
SELECT        GRADE.GRADE_NUM, GRADE.GRADE_NAME
FROM          GRADE
INNER JOIN    STUDENT
ON            GRADE.GRADE_NUM=STUDENT.GRADE_NUM
WHERE         STUDENT.STUDENT_NAME="wonchul"
cs


1
2
3
4
5
6
7
8
9
10
11
@Override
public List<Grade> findGradeJoinNameOfStudent(String name) {
    QGrade grade = QGrade.grade;
    QStudent student = QStudent.student;
 
    return queryFactory
            .selectFrom(grade)
            .join(grade.students, student)
            .where(student.name.eq(name))
            .fetch();
}
cs


- join, leftJoin, rightJoin 등을 사용할 수 있습니다.

- from절에 엔티티를 나열하게되면 cross join이 됩니다.



7. 서브쿼리

1
2
3
4
5
6
7
SELECT  GRADE.GRADE_NUM, GRADE.GRADE_NAME
FROM     GRADE
WHERE     GRADE.GRADE_NUM=(SELECT        GRADE1_.GRADE_NUM
                         FROM         GRADE GRADE1_
                         INNER JOIN STUDENT
                         ON         GRADE1_.GRADE_NUM=STUDENT.GRADE_NUM
                         WHERE         STUDENT.STUDENT_NAME='wonchul')
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public List<Grade> findGradeSubQueryNameOfStudent(String name) {
    QGrade grade = QGrade.grade;
    QStudent student = QStudent.student;
    return queryFactory
            .selectFrom(grade)
            .where(grade.gradeNum.eq(queryFactory
                                    .select(grade.gradeNum)
                                    .from(grade)
                                    .join(grade.students, student)
                                    .where(student.name.eq(name)))
            )
            .fetch();
}
cs



8. DELETE

1
2
3
DELETE 
FROM GRADE
WHERE GRADE_NUM = 3
cs


1
2
3
4
5
6
7
8
9
10
11
12
@Override
public Long deleteByNum(Integer num) {
    QGrade grade = QGrade.grade;
    return queryFactory
            .delete(grade)
            .where(grade.gradeNum.eq(num))
            .execute();
}
 
...
 
gradeRepository.deleteByNum(3)
cs



9. UPDATE

1
2
3
UPDATE GRADE
SET    GRADE_NAME = "<1>학년"
WHERE  GRADE_NUM  = 1
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public Long setFixedNameByNum(Grade grade) {
    QGrade _grade = QGrade.grade;
    
    return queryFactory
            .update(_grade)
            .where(_grade.gradeNum.eq(grade.getGradeNum()))
            .set(_grade.gradeName, grade.getGradeName())
            .execute();
}
 
...
 
gradeRepository.setFixedNameByNum(new Grade(1"<1>학년"));
cs


참고

 

Querydsl 레퍼런스

Gitbub 예제

'web' 카테고리의 다른 글

JPA - Persistence Context (영속성 컨텍스트)  (421) 2017.03.16
Spring Boot - Security + JWT  (423) 2017.02.13
Spring Boot - jar로 Deploy(배포)하기  (405) 2017.01.19
Spring Boot - Apache proxy를 이용한 로드밸런싱  (437) 2017.01.17
Spring Boot - RabbitMQ  (395) 2017.01.10
Comments