SPRING FRAMEWORK

QueryDsl에서 서브쿼리 사용하기

Ambitions 2021. 9. 13. 11:42

최근에 비슷한 유형의 일들만 하다보니 글을 쓸만한 주제가 없었는데, 마침 지금 개발하고있는 부분에서 QueryDsl의 서브쿼리를 사용할일이 생겨서 개발하면서 알게된 점들을 간략하게나마 정리해본다.

 

먼저 QueryDsl에서 서브쿼리는 인라인뷰(from절 서브쿼리)를 제외하고는 사용할 수 있도록 지원하고 있으며 개인적인 생각으로 from절 서브쿼리는 대부분의 경우에 join을 통해 해결할 수 있다고 생각하기 때문에 지원하지 않는다고해서 문제가 되지는 않는다고 생각한다.  

QueryDsl의 의존성은 다음과 같이 추가해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
<dependency> <!-- QueryDsl JPA 라이브러리 -->
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency>
 
<dependency> <!-- Q도메인 생성 라이브러리 -->
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
cs

작업하면서 자바 ORM 표준 JPA 프로그래밍을 참고하고 공식문서들을 참조해서 코드를 작성했고, 책 내용과는 조금 다르게 코드를 작성했다. 대략 다음과 같이 코드들을 작성했다.

 

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@Component // 책내용이나 정석은 인터페이스를 이용하여 Bean을 생성하는게 정석적인 방법임.
public class TestImpl extends QuerydslRepositorySupport {
        
    public TestImpl() {
        super(TestImpl .class);
    }
 
    /* 프로젝트 구조에 따라 추가가 필요한 경우가 있음(여러 개의 DB를 사용하는 경우) 
        @PersistenceContext(unitName = "naraeEmrPacsPlusPersistence")
        @Override
        public void setEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
        }
    */
 
    
    //QueryDsl을 사용할 메소드(인터페이스를 이용하는 경우 override메소드)
    private void getData(long param1, String param2, String param3) {
        
        // 책에서는 JPAQuery를 사용하는 것으로 설명되어 있는데, 
        // 나의 경우 select절 부터 사용할 것이기때문에 JPQLQueryFactory를 사용한다.    
        JPQLQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());
        BooleanBuilder booleanBuilder = new BooleanBuilder(); //조건절 생성을 위한 BooleanBuilder선언
        QTest qtest = QTest.test; // Test모델의 qdomain 선언
        QOther qother = QOther.other; //other모델의 qdomain
        
        booleanBuilder.and(qtest.id.contains("1")); // 아이디에 1포함되는 경우 참
        booleanBuilder.and(qtest.type.eq("test")); // 타입이 test인 경우 참
 
 
        //where절 subQuery : qother의 id를 가져오는 서브쿼리를 qtest의 value조건에 추가
        List<Test> result = queryFactory.select(qtest).from(qtest).where(booleanBuilder,
            qtest.value.in(JPAExpressions.select(qother.id).from(qother))).fetch();
        
 
        //select절 subQuery : 리턴값에 qtest의 개수를 가져오는 쿼리를 추가 + Expressions.as를 이용해 alias를 count로설정
        List<TestDTO> result = queryFactory.select(Projections.constructor(TestDTO.class, qtest.id, qtest.type, qtest.value,
            Expressions.as(JPAExpressions.selectFrom(qtest.count()),"count"))).from(qtest).where(booleanBuilder).fetch(); 
 
    }
 
    //TestDto 클래스
    public class TestDTO {
        private Long id;
        private String type;
        private String value;
        private String count;
 
        @QueryProjection
        public TestDto(Long id, String type, Strin value, String count) {
            this.id = id;
            this.type = type;
            this.value = value;
            this.count = count;
        } 
    }
 
}
 
cs

QueryDsl에서 DTO사용에 관한건 이전에 작성했던 내용을 보면 될 것 같고, 

 

Querydsl에서 DTO를 사용하는 방법

개발을 하고 있는데, 서로 연관 관계가 없는 테이블간에 데이터를 가져와야 할 일이 있었다, 각 Entity들에 @OneToMany나 @ManyToOne 어노테이션을 이용해 데이터를 가져오는 방법도 고려해보았으나, 위

www-swpro-com.tistory.com

Expressions.as는 alias를 추가해주는 기능으로 Expressions클래스내에선언되어있다. 외에 클래스 내에 template 메소드를 사용할 수도있는 것 같은데 나는 as메소드를 사용했다. 

또 서브쿼리를 만들 수 있게해주는 JPAExpressions객체에는 select 외에 selectDistinct, selectZero등의 메소드도 있어서 상황에 따라 사용할 수 있을 것 같다.