Spring/Querydsl

Querydsl - 시작하기

WangTak 2021. 12. 21. 19:16
반응형

Spring Data JPA를 사용하는 기업들이 많아지면서 그와 함께 Querydsl을 사용하는 기업들도 자연스럽게 증가했습니다. 오늘은 Querydsl을 사용하기 위한 프로젝트 환경설정 방법과 잘 적용이 됐는지 동작을 확인해보도록 하겠습니다.

 

먼저, Querydsl에 대해 소개해드리면, JPA가 사용되기 이전에는 MyBatis를 많이 사용했습니다. 그러나 JPA가 등장하면서 새로운 프로젝트를 시작하는 회사에서는 JPA + Querydsl을 기본으로 가져가는 구조로 자리가 잡혔습니다. 그러나 JPA는 MyBatis 대비 동적 쿼리를 구현하는 데 있어서 한계가 있습니다. 그리하여 Querydsl은 JPA로 구현하기 힘든 동적 쿼리를 보완해주는 기술입니다. 또한 MyBatis의 경우에는 쿼리를 짠 후에는 직접 어플리케이션을 실행하고 특정 쿼리가 수행될 때 에러가 있는지 없는지를 알 수 있는 런타임 에러가 발생하지만, Querydsl의 경우는 자바로 직접 쿼리를 작성하기 때문에 컴파일단에서 미리 오류를 확인하여 수정할 수 있는 컴파일 에러가 발생하도록 하는 장점도 있습니다.

 

프로젝트 버전

  • 개발 도구: IntelliJ Ultimate
  • Spring Boot: 2.5.7
  • Java 11
  • h2 Database
  • JUnit 5
  • Spring Web, Spring Data JPA, Lombok

 

먼저 Spring Data JPA를 사용해보도록 하겠습니다. 간단하게 Member Entity를 만들고, Repository 생성하여 데이터를 Insert 해보도록 하겠습니다.

 

Member

@Getter
@Entity
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    @Id
    @Column(name = "member_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;

    private String name;

    @Builder
    private Member(String email, String name) {
        this.email = email;
        this.name = name;
    }
}

 

MemberRepository

public interface MemberRepository extends JpaRepository<Member, Long> {
}

 

MemberRepositoryTest

@SpringBootTest
class MemberRepositoryTest {

    @Autowired
    MemberRepository memberRepository;

    @Test
    public void insertMemberTest() {
        Member member = Member.builder()
                .email("wangtak@gmail.com")
                .name("왕탁이")
                .build();

        Member savedMember = memberRepository.save(member);

	// assertj Assertions static import
        assertThat(savedMember.getEmail()).isEqualTo("wangtak@gmail.com");
        assertThat(savedMember.getName()).isEqualTo("왕탁이");
    }
}

 

Member Table

Test를 통해 저장된 데이터

 

JPA가 잘 동작하게 끔 설정을 마치고 Querydsl을 적용해보도록 하겠습니다. Querydsl은 Spring에서 관리하는 라이브러리가 아니기 때문에 start.spring.io에서 추가할 수는 없고 직접 추가해야 합니다. build.gradle에 다음과 같이 추가해주시면 됩니다.

 

build.gradle

plugins {
   id 'org.springframework.boot' version '2.5.7'
   id 'io.spring.dependency-management' version '1.0.11.RELEASE'
   // querydsl 추가
   id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
   id 'java'
}

group = 'com.wangtak'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
}

repositories {
   mavenCentral()
}

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
   implementation 'org.springframework.boot:spring-boot-starter-web'

   // querydsl 추가
   implementation 'com.querydsl:querydsl-jpa'

   runtimeOnly 'com.h2database:h2'

   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
   useJUnitPlatform()
}

// querydsl 세팅
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
   jpa = true
   querydslSourcesDir = querydslDir
}
sourceSets {
   main.java.srcDir querydslDir
}
configurations {
   querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
   options.annotationProcessorPath = configurations.querydsl
}

 

build.gradle에 위와 같이 추가해주시고 Reload All Gradle Projects를 해주세요. (build.gradle 우측 상단에 코끼리 모양 버튼 클릭.) 혹은 Help -> Find Action -> 검색에 Reload All ... 치시고 클릭

 

그 이후엔 아래 그림과 같이 진행합니다.

 

Querydsl 적용하기

 

IntelliJ 기준으로 우측에 Gradle -> 프로젝트의 이름 -> Tasks -> other -> compileQuerydsl 을 클릭해주세요. 그러면 컴파일이 되고 다음과 같은 위치(build -> generated -> querydsl 이하 디렉토리)에 새로운 파일이 생성됩니다.

 

위 사진과 글 처럼 5번의 compileQuerydsl을 실행하였는데 Unable to load class 'com.mysema.codegen.model.Type' 에러가 발생한다면 build.gradle을 다음 글처럼 바꿔서 설정을 해주시기 바
랍니다. (스프링 부트의 버전이 2.6 이상이라면 다음 글로 해결하실 수 있을겁니다.)
 

스프링 부트 2.6 이상, Querydsl 5.0 Unable to load class 'com.mysema.codegen.model.Type' 에러 해결하기

최근에 한 기업의 과제를 진행하였는데, 주제만 정해주고 기술 스택은 자유였습니다. 그래서 start.spring.io를 통해 최신 버전의 스프링 부트(버전 2.6 이상) 프로젝트를 만들었고, 프로젝트에 Spring

wangtak.tistory.com

 

QType Class 위치

 

그러면 이제 저 QMember를 사용하여 저장된 Member Data를 조회해보도록 하겠습니다. 먼저 실무에서 Querydsl을 사용하는 방법은 Custom Repository 인터페이스를 만들어서 인터페이스의 구현체를 만들고 그 구현체에서 순수 JPA 혹은 Querydsl을 사용합니다. JPA Repository에서는 Custom Repository Interface를 상속하여 사용합니다.

 

MemberRepositoryCustom

// Custom Repository Interface
public interface MemberRepositoryCustom {

    List<Member> querydslFindAll();
}

 

MemberRepositoryCustomImpl

// Custom Repository Interface 구현체
// 이쪽에서 순수 JPA or Querydsl을 사용해줌.
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {

    private final JPAQueryFactory jpaQueryFactory;

    // JPAQueryFactory는 Bean으로 등록해서 사용해도 됨.
    // 다른 식으로 사용하는 방법을 나중에 포스팅
    // 또한 이게 가능한 이유는 생성자가 하나일 때는 알아서 @Autowired가 붙음
    public MemberRepositoryCustomImpl(EntityManager em) {
        jpaQueryFactory = new JPAQueryFactory(em);
    }

    @Override
    public List<Member> querydslFindAll() {
        return jpaQueryFactory
                    .selectFrom(QMember.member)
                    //.selectFrom(member) // QMember를 static import하면 다음과 같이 축약 가능
                    .fetch();
    }
}

 

MemberRepository

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}

 

MemberRepositoryTest

@Test
public void querydslFindAllTest() {
    List<Member> members = memberRepository.querydslFindAll();

    members.iterator().forEachRemaining(member -> {
        System.out.println("member = " + member);
    });
}

Querydsl 실행 결과

 

여기서 MemberRepositoryCustom, MemberRepositoryCustomImpl에 스프링 빈으로 등록하겠다는 어노테이션 없이 자연스럽게 사용되는 것을 알 수 있습니다. 이것은 Spring Data 내부에서 사용자 정의 인터페이스 명 + Impl과 같이 사용하게 되면 알아서 스프링 빈으로 등록해줍니다. [사용자 정의 레파지토리]
일종의 관례고 규칙이기 때문에 "인터페이스 명 + Impl"은 지키는 것이 좋습니다.

 

이렇게 Querydsl을 적용해보고 확인해봤습니다. 순수 JPA, Spring Data JPA를 처음 접하고 사용했을 때 정말 놀라웠고 Querydsl 또한 정말 박수가 나올 만큼 엄청난 기술이라고 생각합니다. 다음에는 더 자세한 Querydsl의 내용을 정리해보도록 하겠습니다.

 

 

반응형