μ• μ •μ½”λ”© πŸ’»

WEB/JPA 2022.03.15 λŒ“κΈ€ 0개 Joana

[Querydsl] λ‚΄κ°€ μ°Ύμ•„ μ“°λ €κ³  μ •λ¦¬ν•œ κΈ€

 

 

πŸƒ‍♂️ κ³„μ†ν•΄μ„œ μ—…λ°μ΄νŠΈ ν•˜κΈ° πŸƒ‍♂️ 

λ§ˆμ§€λ§‰ μ—…λ°μ΄νŠΈ 2022/03/29

 

1. Querydsl 을 μ™œ μ‚¬μš©ν• κΉŒ?

2. μž‘λ™λ°©μ‹?

3. μ˜μ‘΄μ„±

4. Repository ꡬ쑰

5. Projection 의 λŒ€ν•œ κ³ μ°° ...

6. 동적쿼리 (BooleanBuilder)

7. ExpressionUtils

8. μ •λ ¬ νƒ€μž…μ— λ”°λ₯Έ μ •λ ¬ 처리

 

 

 

Querydsl 을 μ™œ μ‚¬μš©ν• κΉŒ?

 

JPA λ₯Ό μ‚¬μš©ν•˜λ©΄μ„œ (@Query 포함) 쑰회 κΈ°λŠ₯에 λŒ€ν•œ ν•œκ³„κ°€ μžˆλ‹€.

동적인 쿼리인 경우인데 예λ₯Ό λ“€μ–΄ μ£Όλ¬Έ νŽ˜μ΄μ§€λ₯Ό 검색 ν•œλ‹€κ³  ν–ˆμ„ λ•Œ μΉ΄ν…Œκ³ λ¦¬ or μƒν’ˆλͺ… or κΈ°μ—…λͺ… λ“±λ“±... 으둜 검색 쑰건이 λ‹¬λΌμ§€λŠ” 뢀뢄이닀.

 

κ·Έλž˜μ„œ μ‚¬μš©ν•˜κ²Œ 된 것이 λ°”λ‘œ Querydsl ν”„λ ˆμž„μ›Œν¬ 이닀.

- νƒ€μž… 체크가 λ°”λ‘œ κ°€λŠ₯ν•˜λ‹€

- μžλ°” μ½”λ“œλ₯Ό 기반으둜 쿼리λ₯Ό μž‘μ„±ν•œλ‹€

 

μž‘λ™λ°©μ‹?

Querydsl -> JPQL -> SQL 

 

 

μ˜μ‘΄μ„±

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

    <dependency>
      <groupId>com.querydsl</groupId>
      <artifactId>querydsl-apt</artifactId>
    </dependency>
    
    <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>

μ—”ν‹°ν‹°λ₯Ό 기반으둜 Q κ°€ λΆ™λŠ” ν΄λž˜μŠ€λ“€μ„ μžλ™ 생성해쀀닀.

λΉŒλ“œλ₯Ό 해보면 target/generated-sources/java 여기에 Qν΄λž˜μŠ€λ“€μ΄ μƒκ²¨λ‚œκ±Έ λ³Ό 수 μžˆλ‹€.

 

- querydsl-jpa : QueryDSL JPA 라이브러리

- querydsl-apt : 쿼리 νƒ€μž…(Q)을 생성할 λ•Œ ν•„μš”ν•œ 라이브러리

 

 

 

Repository ꡬ쑰

public interface UserRepository extends JpaRepository<Users, Long>, UserRepositoryCustom {
}
public interface UserRepositoryCustom {
  List<Users> findByOrderByUser(Long userId, UserSearch userSearch);
}

 

public class UserRepositoryImpl extends QuerydslRepositorySupport implements
  UserRepositoryCustom {

  private JPAQueryFactory queryFactory;
	// 1.
  public UserRepositoryImpl() {
    super(Users.class);
  }
	// 2.
  @Override
  public void setEntityManager(EntityManager entityManager) {
    super.setEntityManager(entityManager);
    queryFactory = new JPAQueryFactory(entityManager);
  }

 

1. QuerydslRepositorySupportλ₯Ό μƒμ†ν•˜κ²Œλ˜λ©΄ 더 이상 QueryDsl을 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

단, μƒμ„±μžμ—μ„œ super(Entity.class)λ₯Ό ν˜ΈμΆœν•˜λ©΄ κ°€λŠ₯ ν•˜λ‹€.

 

- QuerydslRepositorySupport ν΄λž˜μŠ€μ—λŠ” κΈ°λ³Έμƒμ„±μžκ°€ μ—†μŒ.

 

2.

QuerydslRepositorySupport λ₯Ό μ‚¬μš©ν•˜λ©΄ κ΅¬ν˜„μ²΄μ˜ JPQLQuery λ₯Ό μ‚¬μš©ν•΄μ„œ 쿼리λ₯Ό 진행해야 ν•˜κΈ° λ•Œλ¬Έμ— from 으둜 μ‹œμž‘ν•΄μ•Ό ν•œλ‹€. κ·Έλž˜μ„œ queryFactory (일반적으둜 μ‚¬μš©ν•˜λŠ” select 둜 μ‹œμž‘ν•˜κΈ° μœ„ν•΄ )λ₯Ό μ΄μš©ν•˜κΈ° μœ„ν•΄ 

EntityManager λ₯Ό μƒμœ„ ν΄λž˜μŠ€μ— μ „λ‹¬ν•˜μ—¬ JPAQueryμ—μ„œ μ œκ³΅ν•΄μ£ΌλŠ” (select,selectFrom λ“±λ“± ..) 을 κ΅¬ν˜„ν•  수 μžˆλ‹€.

 

-> μ„±λŠ₯ μ°¨μ΄λŠ” μ—†μ§€λ§Œ 일반적으둜 μ‚¬μš©ν•˜λŠ” SQL둜 κ΅¬ν˜„ν•΄μ„œ μ½”λ“œ 가독성을 λ†’μ΄λŠ” 것 λ˜ν•œ 이득이라고 μƒκ°ν•œλ‹€!

 

 

Projection 의 λŒ€ν•œ κ³ μ°° ...

 

쿼리λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•˜λ‚˜μ˜ ν•„λ“œλ₯Ό 쑰회 ν•˜λŠ” 것은 쉽닀.

List<String> result = queryFactory
		.select(users.name)
		.from(users)
		.fetch();

ν•˜μ§€λ§Œ 보톡 μ—¬λŸ¬ 리턴값이 ν•„μš”ν•œλ° μ•„λž˜μ™€ 같이 μž‘μ„±ν•œλ‹€λ©΄ Tuple 둜 λ°˜ν™˜λœλ‹€.

μš°λ¦¬λŠ” κ²°κ³Ό 값을 객체둜 λ§Œλ“€μ–΄ μ‚¬μš©ν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— DTO 둜 λ§Œλ“€μ–΄ μ“°λŠ” 방법에 λŒ€ν•΄ μ•Œμ•„λ³΄κ³ μž ν•œλ‹€!

List<Tuple> result = queryFactory
		.select(users.name,user.age)
		.from(users)
		.fetch();

 

Projections 을 μ‚¬μš©ν•˜λ©΄ λœλ‹€.

1. constructor

μƒμ„±μž 기반 생성

-> λΆˆλ³€ 객체 μ΄μ§€λ§Œ dto μƒμ„±μžμ˜ νŒŒλΌλ―Έν„° μˆœμ„œμ™€ 동일해야 ν•œλ‹€.(ν•„λ“œκ°€ λ§Žμ„μˆ˜λ‘ μ‹€μˆ˜ μœ„ν—˜μ΄ μžˆλ‹€.)

-> 이름을 λ§žμΆ°μ£Όμ§€ μ•Šμ•„λ„ ν•΄λ‹Ήμžλ¦¬μ— λ“€μ–΄κ°€λŠ” 값을 λ„£μ–΄μ€€λ‹€

List<UserInfoDto> result = queryFactory
	.select(
        Projections.constructor(UserInfoDto.class,
        users.name,
        user.age
        ))
	.from(users)
	.fetch();

2. bean

DTO에 μžˆλŠ” setter λ©”μ„œλ“œ(@Setter) 기반 생성

-> DTOκ°€ λΆˆλ³€ 객체가 μ•„λ‹ˆκ²Œ 되기 λ•Œλ¬Έμ— μ§€μ–‘ν•˜λŠ” 방법

List<UserInfoDto> result = queryFactory
	.select(
        Projections.bean(UserInfoDto.class,
        users.name.as("userName"),
        user.age.as("userAge")
        ))
	.from(users)
	.fetch();

3. Fields

ν•„λ“œμ— 직접 μ ‘κ·Όν•΄μ„œ 값을 μ±„μ›Œμ€€λ‹€.

-> ν•„λ“œμ— 직접 μ ‘κ·Όν•˜κΈ° λ•Œλ¬Έμ— 이름이 틀리면 μ•ˆλœλ‹€

-> DB 컬럼 이름과 DTO ν•„λ“œ 이름이 λ‹€λ₯Ό 경우 .as 둜 λ³€κ²½ν•˜μ—¬ λ„£μ–΄μ£Όλ©΄ λœλ‹€

List<UserInfoDto> result = queryFactory
	.select(
        Projections.fields(UserInfoDto.class,
        users.name.as("userName"),
        user.age.as("userAge")
        ))
	.from(users)
	.fetch();

4. @QueryProjection

DTO 에 λŒ€ν•œ Q class 기반 생성

-> DTO μƒμ„±μžμ— @QueryProjection μ–΄λ…Έν…Œμ΄μ…˜μ„ μ„ μ–Έν•˜κ³  querydsl λ₯Ό 컴파일 ν•˜λ©΄ QUserInfoDto.class 처럼 Q ν΄λž˜μŠ€κ°€ μƒμ„±λœλ‹€.

-> Q 클래슀 생성을 미리 ν•΄μ•Ό ν•œλ‹€.

-> DTO κ°€ querydsl 에 μ˜μ‘΄ν•œλ‹€λŠ” 단점이 μžˆλ‹€.

List<UserInfoDto> result = queryFactory
	.select(
        new QUserInfoDto(
        users.name.as("userName"),
        user.age.as("userAge")
        ))
	.from(users)
	.fetch();

 

λ‚˜λŠ” ν•„λ“œλ₯Ό 주둜 μ‚¬μš©ν• κ²ƒ κ°™λ‹€. 

1. μƒμ„±μž - μ‹€λ¬΄μ—μ„œλŠ” μ ˆλ•Œ μ‚¬μš© λͺ»ν• κ²ƒ κ°™λ‹€ dto ν•„λ“œκ°€ 10개 이상 일 λ•Œκ°€ λ§Žλ‹€

2. setter -  λΆˆλ³€μ„± 객체가 μ•„λ‹λ•Œ λΆˆμ•ˆν•˜λ‹€

3. QueryProjection - Qν΄λž˜μŠ€κ°€ μΆ”κ°€λœλ‹€λŠ”κ²Œ λ§˜μ— μ•ˆλ“€κ³  querydsl 에 의쑴이 강해지기 λ•Œλ¬Έμ— μœ μ§€λ³΄μˆ˜μ—μ„œ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμ„ 것 κ°™λ‹€.

 

 

동적쿼리 (BooleanBuilder)

검색 λ“±μ—μ„œ μ‚¬μš©λ  수 μžˆλ„λ‘ 쿼리문에 μž‘μ„±λ˜λŠ” 쑰건듀을 nullable ν•˜κ²Œ 넣어쀄 수 있게 ν•΄μ€€λ‹€.

 

1. μœ μ €λ₯Ό 검색할 λ•Œ μ΄λ¦„μ΄λ‚˜ λ‚˜μ΄λ‘œ κ²€μƒ‰ν•˜κ³  μ‹Άλ‹€

2. μ΄λ¦„ν•„λ“œλ‘œ 값을 받은 경우 builder.and(users.name.eq(name)) 쿼리가 μž‘λ™ν•œλ‹€.

    BooleanBuilder builder = new BooleanBuilder();
    
    if (name != null) {
      builder.and(users.name.eq(name));
    }
    if (age != null) {
      builder.and(users.age.eq(age));
    }
    
    return jpaQueryFactory
            .selectFrom(users)
            .where(builder)
            .fetch();

 

μ‹€λ¬΄μ—μ„œλŠ” 쒀더 가독성을 μ’‹κ²Œ ν•˜κΈ° μœ„ν•΄ μ•„λž˜μ™€ 같이 μ”λ‹ˆλ‹€

return jpaQueryFactory
            .selectFrom(users)
            .where(eqName(name), eqAge(age))
            .fetch();
  }
 
  private BooleanExpression eqName(String name) {
    return name != null ? users.name.eq(name) : null;
  }
 
  private BooleanExpression eqAge(int age) {
    return age == null ? users.age.eq(age) : null;
  }

 

ExpressionUtils

http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/core/types/ExpressionUtils.html

 

ExpressionUtils (Querydsl 4.4.0 API)

Converts the given object to an Expression Casts expressions and wraps everything else into co

querydsl.com

곡식 λ¬Έμ„œμ— 보면 μ–΄λ–€ λ©”μ„œλ“œκ°€ μžˆλŠ”μ§€ 확인할 수 μžˆλ‹€!

νšŒμ‚¬μ—μ„œ λ ˆκ±°μ‹œ ν”„λ‘œμ νŠΈ μˆ˜μ •ν•˜λŠ” 것을 λ΄€λŠ”λ° ExpressionUtils 둜 Subquery λ₯Ό κ΅¬ν˜„ν•˜λŠ”κ±Έ 보고 μ •λ¦¬ν•©λ‹ˆλ‹€!

ν˜„μž¬ ν”„λ‘œμ νŠΈμ—μ„œλŠ” μ—”ν‹°ν‹°λ‘œ κ΅¬μ„±λ˜μ–΄μžˆκΈ° λ•Œλ¬Έμ— μ„œλΈŒμΏΌλ¦¬κ°€ ν•„μš”ν•œ 일은 거의 μ—†μ—ˆλ‹€!

 

return jpaQueryFactory
            .select(Projections.fields(
            ExpressionUtils.as(getUserCount(UserStatus.νƒˆν‡΄νšŒμ›),"usersCount"))
            .from(users).fetchCount());
            
            


private JPQLQuery<Long> getUserCount(UserStatus status){
	return	select(users.id.count())
		.from(users)
		.where(status == null ? null : eqUserStatus(status))
}

private Predicate eqUserStatus(UserStatus status){
	return users.status.eq(status);
}

 

μ—¬κΈ°μ„œ as 둜 μ•Œλ¦¬μ•„μŠ€λ₯Ό ν•΄μ£ΌλŠ”λ° νŒŒλΌλ―Έν„°λ‘œ λ°›λŠ” Expression 은 μ•„λž˜μ™€ 같은 νƒ€μž…μ„ 받을 수 μžˆλ‹€. κ·Έλž˜μ„œ JPQLQuery λ₯Ό λ§Œλ“€μ–΄μ„œ νŒŒλΌλ―Έν„°λ‘œ λŒ€μž… ν•΄μ£Όμ—ˆλ‹€!

Predicate λŠ” μžλ°”μ½”λ“œλ‘œ 쑰건문을 ν‘œν˜„(where 절)ν•  수 있게 ν•΄μ£Όμ–΄μ„œ 쑰건문듀을 λ”°λ‘œ 관리할 수 μžˆλ„λ‘ ν•΄μ€λ‹ˆλ‹€~! 

 

 http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/core/types/Expression.html

 

Expression (Querydsl 4.4.0 API)

Expression defines a general typed expression in a Query instance. The generic type parameter is a reference to the type the expression is bound to. The central Expression subinterfaces are

querydsl.com

 

 

 

 

μ •λ ¬ νƒ€μž…μ— λ”°λ₯Έ μ •λ ¬ 처리

 

private OrderSpecifier getOrderBy(Order sortType) {
  switch (sortType) {
    case μ΅œκ·Όκ°€μž…:
      return new OrderSpecifier(Order.DESC, user.createAt);
    case λ†’μ€μ—°λ Ήμˆœ:
      return new OrderSpecifier(Order.ASC, user.age);
    case νšŒμ›μƒνƒœ:
      return new OrderSpecifier(Order.ASC, user.status);
    default:
      return new OrderSpecifier(Order.ASC, user.createAt);
  }
}

 

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.querydsl.core.types;

public enum Order {
  ASC,
  DESC;

  private Order() {
  }
}
λ°˜μ‘ν˜•