on
Querydsl - 시작하기
Querydsl - 시작하기
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 { }
MemberRepositoryTest
@SpringBootTest class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @Test public void insertMemberTest() { Member member = Member.builder() .email("[email protected]") .name("왕탁이") .build(); Member savedMember = memberRepository.save(member); // assertj Assertions static import assertThat(savedMember.getEmail()).isEqualTo("[email protected]"); 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 이하 디렉토리)에 새로운 파일이 생성됩니다.
QType Class 위치
그러면 이제 저 QMember를 사용하여 저장된 Member Data를 조회해보도록 하겠습니다. 먼저 실무에서 Querydsl을 사용하는 방법은 Custom Repository 인터페이스를 만들어서 인터페이스의 구현체를 만들고 그 구현체에서 순수 JPA 혹은 Querydsl을 사용합니다. JPA Repository에서는 Custom Repository Interface를 상속하여 사용합니다.
MemberRepositoryCustom
// Custom Repository Interface public interface MemberRepositoryCustom { List 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 querydslFindAll() { return jpaQueryFactory .selectFrom(QMember.member) .fetch(); } }
MemberRepository
public interface MemberRepository extends JpaRepository, MemberRepositoryCustom { }
MemberRepositoryTest
@Test public void querydslFindAllTest() { List 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의 내용을 정리해보도록 하겠습니다.
from http://wangtak.tistory.com/12 by ccl(A) rewrite - 2021-12-21 19:27:14