0%

[JPA] QueryDSL 설정방법

JPA에서 QueryDsl를 사용하는 방법에 대해서 알아보자. QueryDSL은 쿼리 domain specific language로 도메인에 맞게 쿼리를 프로그링 할 수 있다는 것이다.

1
2
3
4
5
List<Person> persons = queryFactory.selectFrom(person)
.where(
person.firstName.eq("John"),
person.lastName.eq("Doe"))
.fetch();

이런 느낌!😀

예제 코드

1
2
3
4
5
6
7
8
9
10
11
@Entity
@Table
@Data
public class Account {

@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
}
1
2
public interface AccountRepository extends JpaRepository<Account, Long> {
}

id와 title만 있는 Account엔티티를 정의하고, 기본적으로 JpaReposiotry를 상속받는 레포지토리를 만든다.

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>

다음과 같이 의존성을 주입한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
<plugins>
...
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>

다음과 같은 플러그인을 주입한다. 해당 플러그인은 compile할 때, Post엔티티를 통해서 Query를 만들 수 있는 QAccount라는 클래스를 자동으로 생성해 주는 플러그인이다. 위를 보면, outputDirectory가 QAccount 라는 클래스들이 만들어지는 위치이다. 또한 사용하는 프로세서는 JPAAnnotationProcessor 를 사용한다.

이렇게 추가하고, console 창에 다음과 같이 입력한다.

1
$ mvn clean && compile

clean은 target 디렉토리를 비우는 것이고, compile을 통해서 우리가 원하는 QPost라는 클래스를 생성한다.

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
/**
* QAccount is a Querydsl query type for Account
*/
@Generated("com.querydsl.codegen.EntitySerializer")
public class QAccount extends EntityPathBase<Account> {

private static final long serialVersionUID = 760879443L;

public static final QAccount account = new QAccount("account");

public final StringPath firstName = createString("firstName");

public final NumberPath<Long> id = createNumber("id", Long.class);

public final StringPath lastName = createString("lastName");

public QAccount(String variable) {
super(Account.class, forVariable(variable));
}

public QAccount(Path<? extends Account> path) {
super(path.getType(), path.getMetadata());
}

public QAccount(PathMetadata metadata) {
super(Account.class, metadata);
}

}

QueryDsl를 사용하는 이유가 여러개 있겠지만, 결국 조건절에서 typeSafe하게 비교하는 문장을 만들 수 있기 때문에 사용한다.

1
2
public interface AccountRepository extends JpaRepository<Account, Long>, QuerydslPredicateExecutor<Account> {
}

다음과 같이 QueryDslPredicateExecutor<T> 를 추가함으로써 Predicate 를 사용할 수 있다.

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public interface QuerydslPredicateExecutor<T> {

/**
* Returns a single entity matching the given {@link Predicate} or {@link Optional#empty()} if none was found.
*
* @param predicate must not be {@literal null}.
* @return a single entity matching the given {@link Predicate} or {@link Optional#empty()} if none was found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if the predicate yields more than one
* result.
*/
Optional<T> findOne(Predicate predicate);

/**
* Returns all entities matching the given {@link Predicate}. In case no match could be found an empty
* {@link Iterable} is returned.
*
* @param predicate must not be {@literal null}.
* @return all entities matching the given {@link Predicate}.
*/
Iterable<T> findAll(Predicate predicate);

/**
* Returns all entities matching the given {@link Predicate} applying the given {@link Sort}. In case no match could
* be found an empty {@link Iterable} is returned.
*
* @param predicate must not be {@literal null}.
* @param sort the {@link Sort} specification to sort the results by, may be {@link Sort#empty()}, must not be
* {@literal null}.
* @return all entities matching the given {@link Predicate}.
* @since 1.10
*/
Iterable<T> findAll(Predicate predicate, Sort sort);

/**
* Returns all entities matching the given {@link Predicate} applying the given {@link OrderSpecifier}s. In case no
* match could be found an empty {@link Iterable} is returned.
*
* @param predicate must not be {@literal null}.
* @param orders the {@link OrderSpecifier}s to sort the results by.
* @return all entities matching the given {@link Predicate} applying the given {@link OrderSpecifier}s.
*/
Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);

/**
* Returns all entities ordered by the given {@link OrderSpecifier}s.
*
* @param orders the {@link OrderSpecifier}s to sort the results by.
* @return all entities ordered by the given {@link OrderSpecifier}s.
*/
Iterable<T> findAll(OrderSpecifier<?>... orders);

/**
* Returns a {@link Page} of entities matching the given {@link Predicate}. In case no match could be found, an empty
* {@link Page} is returned.
*
* @param predicate must not be {@literal null}.
* @param pageable may be {@link Pageable#unpaged()}, must not be {@literal null}.
* @return a {@link Page} of entities matching the given {@link Predicate}.
*/
Page<T> findAll(Predicate predicate, Pageable pageable);

/**
* Returns the number of instances matching the given {@link Predicate}.
*
* @param predicate the {@link Predicate} to count instances for, must not be {@literal null}.
* @return the number of instances matching the {@link Predicate}.
*/
long count(Predicate predicate);

/**
* Checks whether the data store contains elements that match the given {@link Predicate}.
*
* @param predicate the {@link Predicate} to use for the existence check, must not be {@literal null}.
* @return {@literal true} if the data store contains elements that match the given {@link Predicate}.
*/
boolean exists(Predicate predicate);
}

테스트 케이스를 작성해보자.

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
@DataJpaTest
@RunWith(SpringRunner.class)
public class AccountRepositoryTest {

@Autowired
private AccountRepository accountRepository;

@Test
public void crud() {

Account account = new Account();
account.setFirstName("andrew");
account.setLastName("han");
accountRepository.save(account);

QAccount qAccount = QAccount.account;
Optional<Account> andrew = accountRepository.findOne(qAccount.firstName.eq("andrew"));

Account result = andrew.get();
Assert.assertEquals(result.getFirstName(), "andrew");


}
}

Account 하나를 만들어서 저장하고, QAccount형의 필드 qAccount를 만들어 놓고, 아래와 같이 typeSafe하게 접근해서 사용할 수 있다. 예제에서는 account의 firstName이 “andrew”인 조건절을 찾는 메서드 이다.

1
2
3
4
5
6
7
8
9
10
11
...// 생략
Hibernate:
select
account0_.id as id1_0_,
account0_.first_name as first_na2_0_,
account0_.last_name as last_nam3_0_
from
account account0_
where
account0_.first_name=?

쿼리뭔을 보면, where 조건절의 first_name으로 찾는 것을 확인할 수 있다.

Welcome to my other publishing channels