spring jpa dynamic query(specification , querydsl)

joojong·2023년 7월 22일
0

dynamic query

서버사이드로 주로 사용하는 framework는 nestjs , gin을 사용하지만 클라이언트쪽에서 spring으로 개발하기를 원해서 spring으로 개발하고 있다. typeorm이나 gorm을 사용할 때는 dynamic query를 사용하기 편했는데 spring은 mybatis 밖에 사용해본 적이 없어서 jpa를 제대로 사용하기 위해서 찾아보게 되었다.

계기

@EntityGraph(
  attributePaths = {"community" , "likes"}
)
List<CommunityCommentEntity> findByUser(UserEntity user , Pageable pageable);

JpaRepository EntityGraph를 사용해서 pagination을 하고 fetch를 했을 경우 결과 전체를 가져와서 pagination을 해주기 떄문에 다른 방법이 필요했다.
warn message : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

specification & critrefia || querydsl

선택지는 specification & critrefia || querydsl 두개였는데
querydsl를 선택하지 않은 이유는 package를 impelement를 하고 설정도 따로 해줘야 했기 떄문에 Jpa에 내장된 JpaSpecificationExecutor를 그냥 쓰기로 했다.

사용방법

UserRepository.java

import java.util.UUID;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

@Repository
public class UserRepository extends JpaRepository<UserEntity , UUID> , JpaSpecificationExecutor<UserEntity> {
	
}

repository에 JpaSpecificationExecutor<Entity> 상속받아준다.


UserSpecification.java

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;

public class UserSpecification {
  public static Specification<UserEntity> userIdAndDeviceName(String word) {
    return new Specification<UserEntity>() {
      @Override
      public Predicate toPredicate(Root<UserEntity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        // join할 table의 변수명 , left | right | inner
        Join<UserEntity , DeviceEntity> devices = root.join("devices" , JoinType.LEFT);
        /*
        조건에 따라서 dynamic하게 query를 날릴 수 있다.
        if(condition) {
          returrn builder.and(
          	builder.equal(root.get("userId") , word)
          )
        }
        */
        return builder.or(
          // ex) iphone이라는 단어를 검색했을 때 userId와 devices.name에  iphone가 포함된 row를 찾습니다.
          builder.like(root.get("userId") ,  "%" + word + "%"),
          builder.like(devices.get("name") ,  "%" + word + "%")
        );
      }
    };
  }
}

devices table에 fk로 user table에 fk가 걸려있는 경우 위와 같이 join할 수 있다.

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;

public Page<UserEntity> users(String word , int page , int count) {
	Page<UserEntity> users = this.userRepository.findAll(
        UserSpecification.userIdAndDeviceName(word),
        PageRequest.of(page, count , Sort.by("createdAt").descending())
    );
    return users;
}

Jpa에 정의되어 있는 findAll의 첫번째 파라미터로 spec을 넣어주고 페이징을 할 경우 Pageable을 두번쨰 파라미터에 넣어주면 된다.

select * from users u
	left outer join devices d 
    on u.id = d.users
    where u.user_id like '%iphone%' OR d.name like '%iphone%'

위 처럼 query를 날릴 수 있다.

참고 : dahye-jeong.gitbook.io

profile
Back-end developer

0개의 댓글