스프링 동적쿼리

Panda·2023년 5월 22일
0

Spring

목록 보기
38/45

JPA Repository에서 기본적으로 제공해주는 메소드들과 JPQL을 사용을 하다가 단점들이 너무 많이 보여서 대체제를 찾기 위해 동적쿼리에 대해 알아보고 공부를 하였습니다.

동적쿼리란?

▪ 정적쿼리 : 조건에 상관없이 변하지 않는 쿼리
▪ 동적쿼리 : 특정 조건에 따라 변경되는 쿼리

정적쿼리 만으로는 비즈니스 로직을 수행하기가 어렵습니다.
따라서 동적쿼리를 사용하는 경우가 많기 때문에 스프링에서는 어떠한 동적쿼리가 있는지 알아봅시다.

JPQL

// entityManager를 통한 JPQL 실행
public List<Member> jpqlTest(String role) {
    String jpql = "select m from Member m";
    String whereSql = " where ";
    if (StringUtils.equls(role, "USER")) {
        jpql += whereSql + "m.role = :role";
    }
   
    TypedQuery<Member> query = entityManager.createQuery(jpql, Member.class);
    query.setParameter("role", role);
    
    ...   
    
    List<Member> members = query.getResultList();
    return members
}

// JPA Repository
public interface PostLikeRepository extends JpaRepository<PostLikeEntity, Long>{
	@Query("select postLike from PostLikeEntity postLike " +
            "where postLike.user.userId = :userId and postLike.post.postId = :postId")
    PostLikeEntity findByUserIdAndPostId(Long userId, Long postId);
}
  1. EntityManager를 통한 JPQL 실행
  2. JPA Repository @Query 어노테이션을 통한 JPQL 작성 및 실행

단점

가장 큰 단점은 문자열을 통해서 동적쿼리를 작성하고 있다는 것입니다.
이 뜻은 문자열로 작성한 쿼리문에 대한 문법 오류를 잡아주지 못하는 단점이 있습니다.
쿼리를 실행하기 전까지는 개발자의 실수들이 얼마든지 침범할 수 있는 여지를 남기고 있습니다.

JPA Specification & Criteria

Criteria는 JPQL을 문자열이 아닌 자바 코드로 작성하도록 도와주는 빌더 클래스 API입니다.

// Criteria만 사용 시
public List<Member> testCriteria(String role) {
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Member> cq = builder.createQuery(Member.class);

    Root<Member> root = cq.from(Member.class);
    List<Predicate> predicates = new ArrayList<>();

    if (StringUtils.equls(role, "USER")) {
    	predicates.add(builder.equal(root.get("role"), role));
    }
    cq.where(predicates.toArray(new Predicate[0]));

    ...

    TypedQuery<Member> query = entityManager.createQuery(cq);
    List<Member> members = query.getResultList();
    return members;
}

// Specification & Criteria 사용 시
public interface MemberRepository extends JpaRepository<Member, Long>, JpaSpecificationExecutor<Member> {
    List<Member> findAll(Specification<Member> spec);
}

public void testQuery() {
	List<Member> members = memberRepository.findAll(
    	memberSpec.makeSpecification(role)
    );
}

public Specification<Member> makeSpecification(String role) {
    return ((root, query, builder) -> {
        List<Predicate> predicates = new ArrayList<>();
	    if (StringUtils.equls(role, "USER")) {
        	predicates.add(builder.equal(root.get("role"), role));
        }
        ...
        
        return builder.and(predicates.toArray(new Predicate[0]));
    });
}
  1. Criteria만 사용했을 때는 JPQL과 비교를 하면 JPQL의 단점을 해결하였습니다.
    하지만 쿼리를 생성하기 위한 코드가 복잡하기 때문에 가독성이 많이 떨어집니다.
    만약 저러한 조건들이 많다면 코드는 그만큼 더 복잡해지겠죠

  2. Spring Data JPA에서는 Criteria 사용을 높이기 위해 Specification 를 제공하고 있습니다. 이를 사용하기 위해서는 JPA Repository에서 JpaSpecificationExecutor 상속 받아야합니다.

Specification 과 Criteria 를 함께 쓰게된다면 Criteria만 사용했을 때보다 훨씬 가독성이 좋은 모습을 볼 수 있습니다.

단점

하.지.만. 그래도 여전히 코드의 형태가 SQL 적이지 않고 직관적이지 않습니다.
저 코드를 분석하면 어떠한 쿼리문이 실행되겠다라고 이해를 하겠지만 분석 하는데 시간을 소요하게 됩니다.

QueryDSL

public List<Member> findMember(String role) {
    BooleanBuilder builder = new BooleanBuilder();
    if (StringUtils.equls(role, "USER")) {
        builder.and(member.gitHubId.contains(gitHubId));
    }
    
    JPAQuery<Member> jpaQuery = jpaQueryFactory
    		.selectFrom(member)
            .where(builder);

    return jpaQuery.fetch();
}

보시면 알겠지만 진짜 SQL 작성하는 것처럼 코드를 작성하게 되어서 직관적입니다.

단점

QueryDSL은 Q.Class에 의존하여 쿼리를 생성하는 것도 단점이라고 볼 수 있습니다. Q.Class를 생성하는 방법이 버전과 빌드 방식에 따라 달라진다는 것입니다. Gradle 빌드해서 생성하는 방식도 있고 Annotation Processor 사용하는 방식도 있습니다.

하지만 설정만 잘해준다면 QueryDSL은 괜찮은 동적쿼리 방식인 것 같습니다.

느낀 점

JPA Repository의 단점을 매우 느끼고 있는 중에 동적쿼리를 공부하게 되었습니다.
이렇게 동적쿼리를 사용한다면 다양한 서비스들을 만들 수 있을 것 같습니다.

제가 나중에 프로젝트를 하게 된다면 QueryDSL 또는 Mybatis 사용을 선호할 것 같습니다.
그리고 사용할 떄는 추가적으로 깊게 다시 공부할 것 같습니다.

나중에 Mybatis도 한번 공부해서 블로그 글을 올릴 예정입니다.
(솔직히 실제 SQL이라 가장 좋을지도 읍읍)

profile
실력있는 개발자가 되보자!

0개의 댓글