[ 김영한 Querydsl #1 ] - Querydsl 설정

정동욱·2023년 7월 15일
0
post-thumbnail

김영한님의 Querydsl 강의를 듣기 시작했습니다. 배치와 시큐리티를 토이 프로젝트에 적용을 해보는 중인데, 중간에 강의도 들어야겠다 싶어서 듣고 있습니다.

우선 프로젝트의 설정은 아래와 같습니다.

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.1.1'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.io'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// Querydsl 추가
	implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
	annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
	useJUnitPlatform()
}

// Querydsl 추가
clean {
	delete file('src/main/generated')
}

Querydsl은 설정이 조금 까다롭습니다. 디펜던시를 따로 받아야 하고 Q클래스(QueryType)라고 하는 것을 별도로 뽑아내야 하는데요, 그것을 위한 설정입니다. 위 설정을 마치고서 Main() 매서드를 실행시키면 generated라는 패키지 아래에 엔티티로 등록한 도메인들의 Q클래스가 생성됩니다. Querydsl은 이렇게 만들어진 Q클래스를 통해 쿼리문을 작성합니다.

Querydsl을 사용하기 위해서는 2개의 객체가 필요합니다. EntityManagerJPAQueryFactory인데요, 알다시피 EntityManager는 영속성 컨텍스트를 관리하는 객체이고 JPAQueryFactoryQuerydsl을 사용하기 위한 객체입니다.

먼저 김영한님이 애정하는 Member와 Team 엔티티를 만들어보겠습니다.

@Entity
@NoArgsConstructor
public class Member {

    @Id
    @Column(name = "member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}
@Entity
@NoArgsConstructor
public class Team {

    @Id
    @Column(name = "team_id")
    private Long id;
    private String name;
}

이제 이 엔티티들을 이용해 Querydsl을 사용해볼텐데요, 그 전에 고정으로 사용할 Member와 Team 객체를 생성해놓고 JPQL로 Member를 조회하는 쿼리문을 작성해보겠습니다.

@BeforeEach
void before() {
	queryFactory = new JPAQueryFactory(em);
	Team teamA = new Team(1L, "teamA");
	Team teamB = new Team(2L, "teamB");

	em.persist(teamA);
	em.persist(teamB);

	Member memberA = new Member(1L, "memberA", 10, teamA);
	Member memberB = new Member(2L, "memberB", 20, teamA);
	Member memberC = new Member(3L, "memberC", 30, teamB);
	Member memberD = new Member(4L, "memberD", 40, teamB);

	em.persist(memberA);
	em.persist(memberB);
	em.persist(memberC);
	em.persist(memberD);

	em.flush();
	em.clear();
}
@Test
void jpqlTest() {
	Member findmember = em
    		.createQuery("SELECT m FROM Member m WHERE m.username = :username", Member.class)
			.setParameter("username", "memberA")
			.getSingleResult();

	assertThat(findmember.getUsername()).isEqualTo("memberA");
	}

JQPL로도 충분히 쿼리문을 작성할 수 있습니다. 하지만 쿼리문에 오타나 문제가 있을 경우, 컴파일 시에 잡아내지 못하고 런타임 때 에러가 발생한다는 큰 문제가 있습니다. 이를 Querydsl로 바꿔보겠습니다.

@Test
void querydslTest1() {
	JPAQueryFactory queryFactory = new JPAQueryFactory(em);
	QMember m = new QMember("m");

	Member findMember = queryFactory
			.select(m)
			.from(m)
			.where(m.username.eq("memberA"))
			.fetchOne();

	assertThat(findMember.getUsername()).isEqualTo("memberA");
	}

이에 반해 Querydsl는 기본적으로 쿼리를 자바 문법으로 작성하기 때문에, 파라미터에 틀린 값이 들어가게 되면 빨간줄이 생겨 에러를 알려주고 당연히 컴파일도 되지 않습니다. 게다가 Ctrl + Space Bar를 누르면 SQL 문법에 대한 힌트도 주기 때문에 개발자에게 엄청난 도움을 줍니다.

보면 알겠지만 사용하기에 다소 거창하다는 단점이 있는데요, 이 문제도 해결법이 다 있습니다. 가장 먼저 매 쿼리마다 QueryTypeEntity 객체를 생성해야 하는 번거로움이 있는데요,

QMember m = new QMember("m");

이 문제는 QueryTypeEntity 내부에 생성해놓은 인스턴스를 사용하면 해결됩니다.

@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QMember extends EntityPathBase<Member> {
	....
    public static final QMember member = new QMember("member1");
    ....
}   
@Test
void querydslTest2() {
	JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    
	Member findMember = queryFactory
			.select(QMember.member)
			.from(QMember.member)
			.where(QMember.member.username.eq("memberA"))
			.fetchOne();

	assertThat(findMember.getUsername()).isEqualTo("memberA");
}

그런데도 보다시피 아래처럼 QueryTypeEntity을 명시하고 있는데요,

.select(QMember.member)

이마저도 한번 더 스태틱 임포트를 해줌으로써 해결됩니다.

import static com.io.querydsl.domain.QMember.member;

@Test
void querydslTest3() {
	JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    
	Member findMember = queryFactory
			.select(member)
			.from(member)
			.where(member.username.eq("memberA"))
			.fetchOne();

	assertThat(findMember.getUsername()).isEqualTo("memberA");
}

QMember m = new QMember("m");

를 사용하는 경우는 조인 시 같은 테이블을 사용해야 할 경우 서로를 구분하기 위해 사용하면 됩니다.

따져보면 Querydsl은 JPQL를 조금 더 쉽게 사용하도록 도와주는 도구일 뿐이고, JPQL은 기본적으로 SQL입니다. 이렇게 도구를 다루는 법도 중요하지만, SQL 문법 공부가 더 중요하다는 점을 잊으면 안될 것 같습니다. 다음 글에서는 Querydsl의 기본 문법에 대해 알아보겠습니다.

profile
거인의 어깨 위에서 탭댄스를

0개의 댓글