JPA에서 QueryDsl를 사용하는 방법에 대해서 알아보자. QueryDSL은 쿼리 domain specific language로 도메인에 맞게 쿼리를 프로그링 할 수 있다는 것이다.
List<Person> persons = queryFactory.selectFrom(person)
.where(
person.firstName.eq("John"),
person.lastName.eq("Doe"))
.fetch();
이런 느낌!😀
예제 코드
@Entity
@Table
@Data
public class Account {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
}
public interface AccountRepository extends JpaRepository<Account, Long> {
}
id와 title만 있는 Account엔티티를 정의하고, 기본적으로 JpaReposiotry를 상속받는 레포지토리를 만든다.
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
다음과 같이 의존성을 주입한다.
...
<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 창에 다음과 같이 입력한다.
$ mvn clean && compile
clean은 target 디렉토리를 비우는 것이고, compile을 통해서 우리가 원하는 QPost라는 클래스를 생성한다.
/**
* 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하게 비교하는 문장을 만들 수 있기 때문에 사용한다.
public interface AccountRepository extends JpaRepository<Account, Long>, QuerydslPredicateExecutor<Account> {
}
다음과 같이 QueryDslPredicateExecutor<T>
를 추가함으로써 Predicate 를 사용할 수 있다.
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);
}
테스트 케이스를 작성해보자.
@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”인 조건절을 찾는 메서드 이다.
...// 생략
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으로 찾는 것을 확인할 수 있다.
📚 Related Posts
- JPA - 값 타입(6)
- JPA - 프록시와 연관관계(5)
- JPA - 다양한 연관관계 매핑(4)
- JPA - 연관관계 매핑(기초)(3)
- JPA - 엔티티 매핑(2)
- JPA - 영속성 관리(1)
- [JPA] 연관관계 매핑 (연관관계 편의 메서드)
- [JPA] 엔티티 설계시 주의사항들
- [JPA] Auditing 사용하기
- [JPA]@Transactional를 통한 Optimization
- [JPA] 엔티티 일부 데이터만 조회하는 Projection
- [JPA] save메서드로 살펴보는 persist와 merge 개념
- [JPA] 쿼리메서드(Lookup 전략)
- [JPA] QueryDSL 설정방법
- [JPA] null 처리
- [JPA] 연관관계 매핑 기초(다대일, 연관관계 주인)
- SpringBoot, JPA, H2를 이용한 간단한API 작성
- [JPA] proxy, fetch 전략
- [JPA] 도메인 클래스 컨버터란?
- [JPA] Custom Repository 만들기
- [JPA] Casecade 옵션