[SpringBoot]-Querydsl 테스트(BooleanBuilder, Predicate, BooleanExpression, 동적쿼리)

ACAI BERRY DEVELOVER·2023년 6월 26일
0
post-thumbnail


🥛 Querydsl의 실습은 다음과 같은 상황을 처리한다.

	1. '제목/내용/작성자'와 같이 단 하나의 항목으로 검색하는 경우
    2. '제목+내용'/'내용+작성자'/'제목+작성자'와 같이 2개의 항목으로 검색하는 경우
    3. '제목 + 내용 + 작성자'와 같이 3개의 항목으로 검색하는 경우
    
    

엔티티 클래스에 많은 변수들이 선언되어 있다면 이러한 조합의 수는 엄청 많아지게 된다. 이런 상황을 대비해 상황에 맞게 쿼리를 처리할 수 있는 Querydsl이 필요하다.

☕️ Querydsl의 사용법은 다음과 같다.

  • BooleanBuilder를 생성한다.
  • 조건에 맞는 구문은 Querydsl에서 사용하는 Predicate 타입의 함수를 생성한다.
  • BooleanBuilder에 작성된 Predicate를 추가하고 실행한다,

☕️ 단일 항목 검색 테스트


  • GuestbookRepositoryTests
 @Test
    public void testQuery1(){

        Pageable pageable = PageRequest.of(0,10, Sort.by("gno").descending());
        QGuestbook qGuestbook = QGuestbook.guestbook; //1
        String keyword = "1";
        BooleanBuilder builder = new BooleanBuilder(); //2
        BooleanExpression expression = qGuestbook.title.contains(keyword); //3
        builder.and(expression); //4
        Page<Guestbook> result = repository.findAll(builder, pageable); //5

        result.stream().forEach(guestbook -> {
            log.info(guestbook);
        });

    }
  • sql
Hibernate: 
    select
        g1_0.gno,
        g1_0.content,
        g1_0.moddate,
        g1_0.regdate,
        g1_0.title,
        g1_0.writer 
    from
        guestbook g1_0 
    where
        g1_0.title like ? escape '!' 
    order by
        g1_0.gno desc limit ?,
        ?
Hibernate: 
    select
        count(g1_0.gno) 
    from
        guestbook g1_0 
    where
        g1_0.title like ? escape '!'
  1. 동적으로 처리하기 위해 Q도메인 클래스를 얻어온다.(Q도메인 클래스를 이용하면 엔티티 클래스에 선언된 필드들을 변수로 활용할 수 있다.)

  2. BooleanBuilder는 where문에 들어가는 조건을 넣어주는 컨테이너이다.

  3. 원하는 조건은 필드 값과 같이 결합해서 생성한다. BooleanBuilder 안에 들어가는 값은 com.querydsl.core.types.Predicate 타입이어야 한다.(Java에 있는 Predicate 타입이 아니다.)

  4. 만들어진 조건은 where문에 and나 or같은 키워드와 결합시킨다.

  5. BooleanBuilder는 레포지토리에 추가된 QuerydslPredicateExecutor 인터페이스의 findAll()을 사용할 수 있다.

-> 이를 통해 페이지 처리와 동시에 검색 처리가 가능해진다.

☕️ 다중 항목 검색 테스트


@Test
    public void testQuery2(){

        Pageable pageable = PageRequest.of(0,10, Sort.by("gno").descending());

        QGuestbook qGuestbook  = QGuestbook.guestbook;
        String keyword="1";
        BooleanBuilder builder = new BooleanBuilder();
        BooleanExpression exTitle = qGuestbook.title.contains(keyword);
        BooleanExpression exContent = qGuestbook.content.contains(keyword);
        BooleanExpression exAll =  exTitle.or(exContent);

        builder.and(exAll);
        builder.and(qGuestbook.gno.gt(250L));


        Page<Guestbook> result = repository.findAll(builder, pageable);

        result.stream().forEach(guestbook -> {
            log.info(guestbook);

        });


    }


🍮 Querydsl Extension

Querydsl is a framework that enables the construction of statically typed SQL-like queries through its fluent API.

Several Spring Data modules offer integration with Querydsl through QuerydslPredicateExecutor, as the following example shows:

Example 46. QuerydslPredicateExecutor interface
public interface QuerydslPredicateExecutor<T> {

  Optional<T> findById(Predicate predicate);  

  Iterable<T> findAll(Predicate predicate);   

  long count(Predicate predicate);            

  boolean exists(Predicate predicate);        

  // … more functionality omitted.
}

Finds and returns a single entity matching the Predicate.
Finds and returns all entities matching the Predicate.
Returns the number of entities matching the Predicate.
Returns whether an entity that matches the Predicate exists.

To use the Querydsl support, extend QuerydslPredicateExecutor on your repository interface, as the following example shows:

Example 47. Querydsl integration on repositories
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor< User > { }

The preceding example lets you write type-safe queries by using Querydsl Predicate instances, as the following example shows:

Predicate predicate = user.firstname.equalsIgnoreCase("dave")
	.and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

reference: https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories.scrolling.guidance

🍮 Class BooleanBuilder


Class BooleanBuilder
java.lang.Object
com.querydsl.core.BooleanBuilder
All Implemented Interfaces:
Expression, Predicate, Serializable, Cloneable

public final class BooleanBuilder
extends Object
implements Predicate, Cloneable
BooleanBuilder is a cascading builder for Predicate expressions. BooleanBuilder is a mutable Expression implementation.
Usage example:

QEmployee employee = QEmployee.employee;
BooleanBuilder builder = new BooleanBuilder();
for (String name : names) {
builder.or(employee.name.equalsIgnoreCase(name));
}

reference:http://querydsl.com/static/querydsl/4.0.7/apidocs/com/querydsl/core/BooleanBuilder.html

🍮 Complex predicates


To construct complex boolean expressions, use the com.querydsl.core.BooleanBuilder class. It implements Predicate and can be used in cascaded form:

public List< Customer > getCustomer(String... names) {
QCustomer customer = QCustomer.customer;
JPAQuery< Customer > query = queryFactory.selectFrom(customer);
BooleanBuilder builder = new BooleanBuilder();
for (String name : names) {
builder.or(customer.name.eq(name));
}
query.where(builder); // customer.name eq name1 OR customer.name eq name2 OR ...
return query.fetch();
}

BooleanBuilder is mutable and represents initially null and after each and or or call the result of the operation.

reference: http://querydsl.com/static/querydsl/latest/reference/html/ch03.html#d0e2073

🍮 동적쿼리는 BooleanExpression 사용하기


동적쿼리를 작성하는 방법에는 BooleanBuilder 를 작성하는 방법과 Where 절과 피라미터로 Predicate 를 이용하는 방법 그리고 Where 절과 피라미터로 Predicate 를 상속한 BooleanExpression 을 사용하는 방법 이렇게 있다.
예제를 보면 알겠지만 BooleanBuilder 를 사용하는 방법은 어떤 쿼리가 나가는지 예측하기 힘들다는 단점이 있다.
그리고 Predicate 보다는 BooleanExpression 을 사용하는 이유로는 BooleanExpression 은 and 와 or 같은 메소드들을 이용해서 BooleanExpression 을 조합해서 새로운 BooleanExpression 을 만들 수 있다는 장점이 있다. 그러므로 재사용성이 높다. 그리고 BooleanExpression 은 null 을 반환하게 되면 Where 절에서 조건이 무시되기 때문에 안전하다.

BooleanBuilder 를 이용하는 예제

public List<MemberTeamDto> searchByBuilder(MemberSearchCondition condition){
  BooleanBuilder builder = new BooleanBuilder();

  if (hasText(condition.getUsername())) {
      builder.and(member.username.eq(condition.getUsername()));
  }

  if(hasText(condition.getTeamName())){
      builder.and(team.name.eq(condition.getTeamName()));
  }

  if(condition.getAgeGoe() != null) {
      builder.and(member.age.goe(condition.getAgeGoe()));
  }

  if(condition.getAgeLoe() != null){
      builder.and(member.age.loe(condition.getAgeLoe()));
  }

  return queryFactory
          .select(new QMemberTeamDto(
                  member.id.as("memberId"),
                  member.username,
                  member.age,
                  team.id.as("teamId"),
                  team.name.as("teamName")
          ))
          .from(member)
          .leftJoin(member.team, team)
          .where(builder)
          .fetch();
}

Where 절과 BooleanExpression 을 이용하는 에제

public List<MemberTeamDto> searchByWhere(MemberSearchCondition condition){
  return queryFactory
          .select(new QMemberTeamDto(
                  member.id.as("memberId"),
                  member.username,
                  member.age,
                  team.id.as("teamId"),
                  team.name.as("teamName")
          ))
          .from(member)
          .leftJoin(member.team, team)
          .where(
              usernameEq(condition.getUsername()),
              teamNameEq(condition.getTeamName()),
              ageGoe(condition.getAgeGoe()),
              ageLoe(condition.getAgeLoe())
          )
          .fetch();
}

private BooleanExpression usernameEq(String username) {
  return hasText(username) ? member.username.eq(username) : null;
}

private BooleanExpression teamNameEq(String teamName) {
  return hasText(teamName) ? team.name.eq(teamName) : null;
}

private BooleanExpression ageGoe(Integer ageGoe) {
  return ageGoe != null ? member.age.goe(ageGoe) : null;
}

private BooleanExpression ageLoe(Integer ageLoe) {
  return ageLoe != null ? member.age.loe(ageLoe) : null;
}

private BooleanExpression ageBetween(Integer ageLoe, Integer ageGoe) {
  return ageLoe(ageLoe).and(ageGoe(ageGoe));
}

reference: https://velog.io/@youngerjesus/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%98%95%EC%A0%9C%EB%93%A4%EC%9D%98-Querydsl-%ED%99%9C%EC%9A%A9%EB%B2%95

profile
쓸때 대충 쓰지 말고! 공부하면서 써!

0개의 댓글