TIL

[231219] QueryDSL, JPQL

진진리 2023. 12. 19. 16:27
728x90
  • Spring 3.X 버전 연동 설정
dependencies {
    ....

    // 9. QueryDSL 적용을 위한 의존성 (SpringBoot3.0 부터는 jakarta 사용해야함)
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

QueryDSL

  • Entity의 매핑정보를 활용하여 쿼리에 적합하도록 쿼리 전용 클래스(Q클래스)로 재구성해주는 기술

 

장점

  1. 문자가 아닌 코드로 쿼리를 작성함으로써, 컴파일 시점에 문법 오류를 쉽게 확인할 수 있다.
  2. 자동 완성 등 IDE의 도움을 받을 수 있다.
  3. 동적인 쿼리 작성이 편리하다.
  4. 쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용할 수 있다.

1. QueryDslPredicateExecutor 사용

원리

  • QueryDSL 의존성을 추가하면 SpringData에 의해 QueryDslPredicateExecutor 인터페이스가 추가된다.
  • QueryDslPredicateExecutor 인터페이스는 Repository가 QueryDsl 을 실행할 수 있는 인터페이스를 제공하는 역할을 합니다.

기능

  • QueryDSL의 Predicate 인터페이스로 조건문을 여러개를 구성하여 따로 관리할 수 있다.
  • findOne(Predicate), findAll(Predicate) 주로 이 2개 메소드가 사용된다.
    • findOne = Optional<T> 리턴
    • findAll = List<T> | Page<T> | Iterable<T> | Slice<T> 리턴
  • QuerydslPredicateExecutor 활용: Join 연산이 불가능, 대신 조건이 많은 쿼리가 가능
    • 멤버 컬렉션까지만 조회 가능하며 이를 묵시적 조인(1 Depth 자동 조인)이라고 한다.
    • 반면에, Join 연산이 수행되는 건 명시적 조인(2 Depth 이상 조인)이라고 한다.
  • 따라서 JPQL과 JPAQueryFactory로 join 연산하는 법을 배움

실습

  • Q 클래스(쿼리타입)
    • QueryDSL로 쿼리를 작성할 때 Q클래스를 사용함으로써 쿼리를 Type-Safe하게 작성 가능
    • 기본 인스턴스를 사용하면 import static을 활용해서 코드를 더 간결하게 작성 가능
  • 사용할 Repository에서 QueryDslPredicateExecutor를 상속받는다.
public interface ItemRepository extends JpaRepository<Item, Long>, 
	QuerydslPredicateExecutor<Item> {
}
  • QueryDSL 사용 예제
QItem item = QItem.item;
Iterable<Item> result = itemRepository.findAll(
	item.name.contains("장난감").and(item.price.between(10000, 20000))
);

 

 

2. JPAQueryFactory 사용

  • JPAQueryFactory: 재구성한 Q클래스를 통해 문자열이 아닌 객체 또는 함수로 쿼리를 작성하고 실행하게 해주는 기술
  • JPAQueryFactory에 entityManager를 주입해 Bean으로 등록해줘야 함
@Configuration
public class JPAConfiguration {

  @PersistenceContext
  private EntityManager entityManager;

  @Bean
  public JPAQueryFactory jpaQueryFactory() {
    return new JPAQueryFactory(entityManager);
  }
}

 

 

사용 예제

public List<User> selectUserByUsernameAndPassword(String username, String password){
	QUser user = QUser.user;

  	List<Person> userList = jpaQueryFactory
			.selectFrom(user)
			.where(person.username.eq(username)
			.and(person.password.eq(password))
			.fetch();
                                
	return userList;
}

JPQL(Java Persistence Query Language)

  • Table이 아닌 Entity(객체) 기준으로 작성하는 쿼리
  • 이를 사용할 수 있도록 Entitymanager 또는 @Query 구현체를 통해 JPQL 쿼리를 사용할 수 있음

1. EntityManager.createQuery()

@Test
public void testEmCreateQuery() {
	String qlString = "select u from User u " +
					"where u.username = :username";

	Member findUser = em.createQuery(qlString, User.class)
			.setParameter("username", "teasun")
			.getSingleResult();

	assertThat(findUser.getUsername()).isEqualTo("teasun");
}

 

2. @Query (repository interface)

  • ?변수순번 사용
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("SELECT u, u.password AS customField FROM User u WHERE u.username = ?1")
  List<User> findByUsernameWithCustomField(String username, Sort sort);


  @Query("SELECT u FROM User u WHERE u.username = ?1")
  List<User> findByUsername(String username, Sort sort);
}
  • :변수명 사용
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("SELECT u, u.password AS customField FROM User u WHERE u.username = :username")
  List<User> findByUsernameWithCustomField(String username, Sort sort);


  @Query("SELECT u FROM User u WHERE u.username = :username")
  List<User> findByUsername(String username, Sort sort);
}