[ 김영한 Querydsl #3 ] - 기본 문법 (2) : 조인, 서브쿼리, case

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

이번 글에서는 Querydsl을 이용한 조인에 대해 알아보겠습니다.

먼저 가장 단순한 조인을 해볼텐데요, teamA에 속한 모든 멤버를 조회해보겠습니다.

@Test
void join() {
	List<Member> result = queryFactory
			.selectFrom(member)
			.join(member.team, team)
			.where(team.name.eq("teamA"))
			.fetch();

	System.out.println("result = " + result);

	assertThat(result)
			.extracting("username")
			.containsExactly("memberA", "memberB");
}

이제는 조금 복잡하게 모든 멤버를 조회하면서 멤버의 팀이 teamA일 경우 teamA도 조회해보겠습니다.

@Test
void joinOnFiltering() {
	List<Tuple> result = queryFactory
			.select(member, team) // 조회 대상이 2개 이상이면 Tuple로 조회
			.from(member)
			.leftJoin(member.team, team) // leftjoin으로 member는 전부, team은 조건으로 필터
			.on(team.name.eq("teamA")) // 조인의 조건
			.fetch();
}

보면 Tuple이라는 타입이 있는데, SQL 조회 시 대상이 2개 이상일 때, 저런 방식으로 받습니다.

그리고 연관관계가 없는 엔티티끼리 조회하는 방법을 알아볼텐데요, 모든 회원을 조회하면서 회원 이름과 팀 이름이 같은 경우에 한해 팀도 조회해보겠습니다.

@Test
void theta_join2() {
	em.persist(new Member(8L, "teamA"));
	em.persist(new Member(9L, "teamB"));
	em.persist(new Member(10L, "teamC"));

	List<Tuple> result = queryFactory
			.select(member, team) // 회원과 팀을 각각 조회
			.from(member)
            // team을 통째로 조인하는데, 조인될 때 조건이 회원 이름 = 팀 이름
			.leftJoin(team).on(member.username.eq(team.name)) 
			.fetch();
}

그리고 fetchJoin도 해볼텐데요, 매우 간단합니다. memberA를 조회하면서 memberA의 팀까지도 한 쿼리로 조회해보겠습니다. 엔티티를 조인하고, 뒤에 fetchJoin()을 붙여주기만 하면 끝입니다!

@Test
void fetchJoin() {
	em.flush();
	em.clear();

	Member findMember = queryFactory
			.selectFrom(member)
			.join(member.team, team)
			.fetchJoin()
			.where(member.username.eq("memberA"))
			.fetchOne();

	System.out.println("findMember = " + findMember);

	assertThat(findMember.getTeam().getName()).isEqualTo("teamA");
}

물론 위의 코드에서 .fetchJoin() 부분을 제거해도 테스트는 성공합니다. 하지만 Member를 조회하는 쿼리, Team을 조회하는 쿼리가 따로 나가기 때문에 fetchJoin를 이용해 1번의 쿼리로 가져오는 것이 좋습니다.

이번엔 서브쿼리를 사용하는 방법에 대해 알아보겠습니다. 서브쿼리를 작성할 때는 동일한 테이블이 또 사용되는 경우도 있는데요, 이때 구분을 위해 Q클래스의 인스턴스를 하나 생성해줍니다. 그리고 JPAExpressions 객체를 서브쿼리를 이용해 작성하면 됩니다. 예시로 서브쿼리를 이용해 가장 나이가 많은 회원을 조회해보겠습니다.

@Test
void subQuery() {
	QMember subMember = new QMember("subMember");
	List<Member> result = queryFactory
			.selectFrom(member)
			.where(member.age.eq(
					JPAExpressions
							.select(subMember.age.max())
							.from(subMember)
			))
			.fetch();

	assertThat(result)
			.extracting("age")
			.containsExactly(40);
}

JPAExpressions 객체는 위처럼 서브쿼리를 만들 때 사용하는 객체입니다. 보면 알겠지만 당연히 스태틱 임포트가 됩니다. 이번엔 in을 이용해 나이가 10살 이상인 회원을 조회해보겠습니다.

@Test
void subQuery3() {
	QMember subMember = new QMember("subMember");
	List<Member> result = queryFactory
			.selectFrom(member)
			.where(member.age.in(
					select(subMember.age)
							.from(subMember)
							.where(member.age.gt(10))
			))
			.fetch();

	assertThat(result)
			.extracting("age")
			.containsExactly(20, 30, 40);
}

마지막으로 case문을 살펴볼텐데 Querydsl에서는 이걸 굉장히 직관적이게 다룹니다. 먼저 나이를 기준으로 회원을 분류를 해보겠습니다.

@Test
void basicCase() {
	List<Tuple> result = queryFactory
			.select(member.username, member.age
					.when(10).then("열살")
					.when(20).then("스무살")
					.when(30).then("서른살")
					.otherwise("기타")
			)
			.from(member)
			.fetch();
}

위는 매우 간단한 case 문을 작성할 때 사용하면 되는데요, 이 방법 말고도 CaseBuilder라는 객체를 이용해서도 작성할 수 있습니다.

@Test
void complexCase() {
	List<Tuple> result = queryFactory
			.select(member.username, new CaseBuilder()
					.when(member.age.between(0, 19)).then("미성년자")
					.when(member.age.between(20, 29)).then("청년")
					.when(member.age.between(30, 39)).then("삼촌")
					.otherwise("아저씨")
			)
			.from(member)
			.fetch();
}

Querydsl을 이용한 조인/서브쿼리/case 문법을 보았는데요, Querydsl가 지원하지 않는 부분도 있습니다. 바로 from 절에서의 서브쿼리 사용인데요, JPQL 자체가 from 절에서 서브쿼리 사용이 불가능합니다. Querydsl도 결국 JPQL 도구이기 때문에 이 부분은 불가능하다는 점을 알면서, 대신 서브쿼리를 조인으로 대체한다던가 아니면 nativeSQL을 사용하는 방법을 모색해야 합니다.

지금까지 가장 기초적인 문법에 대해 배워봤는데요, 다음 글부터는 조금 더 어려운 문법에 대해 배워보겠습니다.

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

0개의 댓글