๐Ÿ’ก Querydsl์ด๋ž€?

QueryDSL์€ ์ •์  ํƒ€์ž…์„ ์ด์šฉํ•ด์„œ SQL๊ณผ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด ์ฃผ๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.
์ฟผ๋ฆฌ๋ฅผ ๋ฌธ์ž์—ด๋กœ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, QueryDSL์ด ์ œ๊ณตํ•˜๋Š” Fluent API๋ฅผ ์ด์šฉํ•ด ์ฝ”๋“œ ์ž‘์„ฑ์˜ ํ˜•์‹์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค.

Querydsl vs JPQL

ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ๋‘ ๊ฐœ์˜ ์ฐจ์ด์ ์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

- JPQL

@Test
 public void startJPQL() {
	//member1์„ ์ฐพ์•„๋ผ. 
	String qlString =
             "select m from Member m " +
             "where m.username = :username";
             
     Member findMember = em.createQuery(qlString, Member.class)
             .setParameter("username", "member1")
             .getSingleResult();
             
     assertThat(findMember.getUsername()).isEqualTo("member1");
}

- Qeurydsl

 @Test
 public void startQuerydsl() {
    //member1์„ ์ฐพ์•„๋ผ.
    JPAQueryFactory queryFactory = new JPAQueryFactory(em); 
    QMember m = new QMember("m");
    Member findMember = queryFactory
                 .select(m)
                 .from(m)
                 .where(m.username.eq("member1"))//ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ์ฒ˜๋ฆฌ 
                 .fetchOne();
    
    assertThat(findMember.getUsername()).isEqualTo("member1");
 }

JPQL์œผ๋กœ ๋ฌธ์ž๋กœ ์ž‘์„ฑ๋˜๊ณ  Querydsl์€ ์ฝ”๋“œ๋กœ ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค.
JPQL์€ ์‹คํ–‰์„ ํ•ด์•ผ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ Querydsl์€ ์ปดํŒŒ์ผ์‹œ์ ์— ์˜ค๋ฅ˜๋ฅผ ์žก์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ, JPQL์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ง์ ‘ ๋ฐ”์ธ๋”ฉํ•ด์•ผํ•˜์ง€๋งŒ, Querydsl์€ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ž๋™์ฒ˜๋ฆฌ ํ•ด์ค๋‹ˆ๋‹ค.


ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ์„ค์ •

querydsl์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ .gradleํŒŒ์ผ์— ๊ด€๋ จ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

	//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"

์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

@Entity
@Getter
@Setter
public class Hello {

     @Id
     @GeneratedValue
     private Long id;

}

๋นŒ๋“œ ํ›„ ํ”„๋กœ์ ํŠธ ํŒŒ์ผ์„ ํ™•์ธํ•ด๋ณด๋ฉด Q ํƒ€์ž…์ด ์ƒ์„ฑ๋œ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๋ฌธ๋ฒ•

๊ธฐ๋ณธ ๊ตฌ์กฐ

๋จผ์ €, querydsl์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ ๊ตฌ์กฐ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

   @PersistenceContext
   EntityManager em;
   
   JPAQueryFactory queryFactory;
   
   @BeforeEach
   public void before() {
       queryFactory = new JPAQueryFactory(em);
	//...
	}
   
   @Test
   public void startQuerydsl2() {
   //member1์„ ์ฐพ์•„๋ผ.
   QMember m = new QMember("m"); //Qํƒ€์ž… ํด๋ž˜์Šค ๊ฐ์ฒด ์ƒ์„ฑ
   
   Member findMember = queryFactory
               .select(m) // select ๋ฉ”์†Œ๋“œ
               .from(m) // from ๋ฉ”์†Œ๋“œ
               .where(m.username.eq("member1")) // where ๋ฉ”์†Œ๋“œ
               .fetchOne(); //fetch๋ฉ”์†Œ๋“œ
               
   assertThat(findMember.getUsername()).isEqualTo("member1");
   }
}
  • querydsl์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” JPAQeuryFactory๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • querydsl์€ ๋นŒ๋” ํŒจํ„ด ๊ตฌ์กฐ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.
    SELECT ์ ˆ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” select ๋ฉ”์„œ๋“œ์—, FROM ์ ˆ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” from ๋ฉ”์„œ๋“œ์—, WHERE์ ˆ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” where ๋ฉ”์†Œ๋“œ์— ์„ธํŒ…ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ฉ”์„œ๋“œ๋“ค์€ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์กฐ๋กœ ๋˜์–ด์žˆ์–ด ๊ฐ€๋…์„ฑ์ด ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค.
  • ์—”ํ‹ฐํ‹ฐ ๋ฐ์ดํ„ฐ๋Š” Qํƒ€์ž… ํด๋ž˜์Šค ๊ฐ์ฒด๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก JPAQueryFactory๋ฅผ ํ•„๋“œ๋กœ ์ œ๊ณตํ•˜๋ฉด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋Š” ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

๋™์‹œ์„ฑ ๋ฌธ์ œ๋Š” JPAQueryFactory๋ฅผ ์ƒ์„ฑ ํ•  ๋•Œ ์ œ๊ณตํ•˜๋Š” EntityManager(em)์— ๋‹ฌ๋ ค์žˆ์Šต๋‹ˆ๋‹ค.
์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์—ฌ๋Ÿฌ ์“ฐ๋ ˆ๋“œ์—์„œ ๋™์‹œ์— ๊ฐ™์€ EntityManager์— ์ ‘๊ทผํ•ด๋„, ํŠธ๋žœ์žญ์…˜ ๋งˆ๋‹ค ๋ณ„๋„์˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋™์‹œ์„ฑ ๋ฌธ์ œ๋Š” ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค!!!

Q-Type

Qํด๋ž˜์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

1. ๋ณ„์นญ์„ ์ง์ ‘ ์ง€์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•

QMember qMember = new QMember("m");

Qํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ• ๋•Œ ์ง์ ‘ ๋ณ„์นญ์„ ์ง€์ •ํ•˜์—ฌ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค ์‚ฌ์šฉ
Qํด๋ž˜์Šค๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‚ด๋ถ€์— ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค๊ฐ€ ์„ ์–ธ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ฐ™์€ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

QMember qMember = QMember.member;

3. ๊ธฐ๋ณธ ์ธ์Šคํ„ด์Šค๋ฅผ static import์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ

 import static study.querydsl.entity.QMember.*;
 
 @Test
 public void startQuerydsl3() {
    //member1์„ ์ฐพ์•„๋ผ.
    Member findMember = queryFactory
                 .select(member)
                 .from(member)
                 .where(member.username.eq("member1"))
                 .fetchOne();
                 
    assertThat(findMember.getUsername()).isEqualTo("member1");
 }

์œ„์™€ ๊ฐ™์ด static import๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋”ฐ๋กœ ์„ ์–ธ์„ ํ•˜์ง€ ์•Š์•„๋„ ๋ฐ”๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ฟผ๋ฆฌ

  • ๋น„๊ต ์—ฐ์‚ฐ
member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'
  • null ๊ฒ€์‚ฌ
member.username.isNotNull() //์ด๋ฆ„์ด is not null
  • ๋ฒ”์œ„ ๋น„๊ต
member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //between 10, 30
  • ํฌ๊ธฐ ๋น„๊ต
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
  • like ์—ฐ์‚ฐ
member.username.like("member%") //like ๊ฒ€์ƒ‰ 
member.username.contains("member") // like โ€˜%member%โ€™ ๊ฒ€์ƒ‰ 
member.username.startsWith("member") //like โ€˜member%โ€™ ๊ฒ€์ƒ‰

๐Ÿ’ก ๊ฒ€์ƒ‰ ์กฐ๊ฑด์€ .and(), .or()๋ฅผ ๋ฉ”์„œ๋“œ ์ฒด์ธ์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 @Test
 public void search() {
     Member findMember = queryFactory
            .selectFrom(member) // select, from์„ selectFrom์œผ๋กœ ํ•ฉ์น  ์ˆ˜ ์žˆ์Œ
            .where(member.username.eq("member1")
                    .and(member.age.eq(10)))
            .fetchOne();
            
    assertThat(findMember.getUsername()).isEqualTo("member1");
}

.and() ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ๋žตํ•˜๊ณ  where()์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๊ฒ€์ƒ‰์กฐ๊ฑด์„ ์ถ”๊ฐ€ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด ๊ฒฝ์šฐ null ๊ฐ’์€ ๋ฌด์‹œ๋˜๋ฉฐ ๋ฉ”์„œ๋“œ ์ถ”์ถœ์„ ํ™œ์šฉํ•ด์„œ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Test
public void searchAndParam() {
     List<Member> result1 = queryFactory
             .selectFrom(member)
		     .where(member.username.eq("member1"),
					member.age.eq(10))
             .fetch();
     
     assertThat(result1.size()).isEqualTo(1);
 }

๊ฒฐ๊ณผ ์กฐํšŒ

  • fetch() : ๋ฆฌ์ŠคํŠธ ์กฐํšŒ, ๋ฐ์ดํ„ฐ ์—†์œผ๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
 	List<Member> fetch = queryFactory
          .selectFrom(member)
          .fetch();
  • fetchOne() : ๋‹จ ๊ฑด ์กฐํšŒ
    ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด : null
    ๊ฒฐ๊ณผ๊ฐ€ ๋‘˜ ์ด์ƒ์ด๋ฉด : com.querydsl.core.NonUniqueResultException
  Member findMember1 = queryFactory
        .selectFrom(member)
  • fetchFirst() : ์ฒ˜์Œํ•œ๊ฑด ์กฐํšŒ = limit(1).fetchOne()
  Member findMember2 = queryFactory
        .selectFrom(member)
        .fetchFirst();
  • fetchResults() : ํŽ˜์ด์ง• ์ •๋ณด ํฌํ•จ, total count ์ฟผ๋ฆฌ ์ถ”๊ฐ€ ์‹คํ–‰
	QueryResults<Member> results = queryFactory
        .selectFrom(member)
        .fetchResults();
  • fetchCount() : count ์ฟผ๋ฆฌ๋กœ ๋ณ€๊ฒฝํ•ด์„œ count ์ˆ˜ ์กฐํšŒ
	long count = queryFactory
         .selectFrom(member)
         .fetchCount();

์ •๋ ฌ

  • .orderBy()
  • desc() , asc() : ์ผ๋ฐ˜ ์ •๋ ฌ
  • nullsLast() , nullsFirst() : null ๋ฐ์ดํ„ฐ ์ˆœ์„œ ๋ถ€์—ฌ
/**
*ํšŒ์› ์ •๋ ฌ ์ˆœ์„œ
* 1. ํšŒ์› ๋‚˜์ด ๋‚ด๋ฆผ์ฐจ์ˆœ(desc)
* 2. ํšŒ์› ์ด๋ฆ„ ์˜ฌ๋ฆผ์ฐจ์ˆœ(asc)
* ๋‹จ 2์—์„œ ํšŒ์› ์ด๋ฆ„์ด ์—†์œผ๋ฉด ๋งˆ์ง€๋ง‰์— ์ถœ๋ ฅ(nulls last) */
 @Test
 public void sort() {
     em.persist(new Member(null, 100));
     em.persist(new Member("member5", 100));
     em.persist(new Member("member6", 100));
     
     List<Member> result = queryFactory
             .selectFrom(member)
             .where(member.age.eq(100))
             .orderBy(member.age.desc(), member.username.asc().nullsLast())
             .fetch();
             
     Member member5 = result.get(0);
     Member member6 = result.get(1);
     Member memberNull = result.get(2);
     
     assertThat(member5.getUsername()).isEqualTo("member5");
     assertThat(member6.getUsername()).isEqualTo("member6");
     assertThat(memberNull.getUsername()).isNull();
}

ํŽ˜์ด์ง•

: ์กฐํšŒ ๊ฑด์ˆ˜ ์ œํ•œ

 @Test
 public void paging1() {
     List<Member> result = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc()) .offset(1) //0๋ถ€ํ„ฐ ์‹œ์ž‘(zero index) 
            .limit(2) //์ตœ๋Œ€ 2๊ฑด ์กฐํšŒ
            .fetch();
     assertThat(result.size()).isEqualTo(2);
 }

๐Ÿ’ก ํŽ˜์ด์ง• ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ, ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๋Š” ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•ด์•ผ ํ•˜์ง€๋งŒ, count ์ฟผ๋ฆฌ๋Š” ์กฐ์ธ์ด ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ž๋™ํ™”๋œ count ์ฟผ๋ฆฌ(.fetchResults์™€ ๊ฐ™์€)๋Š” ์›๋ณธ ์ฟผ๋ฆฌ์™€ ๊ฐ™์ด ๋ชจ๋‘ ์กฐ์ธ์„ ํ•ด๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ด ์•ˆ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. count ์ฟผ๋ฆฌ์— ์กฐ์ธ์ด ํ•„์š”์—†๋Š” ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด, count ์ „์šฉ ์ฟผ๋ฆฌ๋ฅผ ๋ณ„๋„๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ง‘ํ•ฉ

  • ์ง‘ํ•ฉํ•จ์ˆ˜
    JPQL์ด ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“  ์ง‘ํ•ฉ ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
 @Test
 public void aggregation() throws Exception {
 
     List<Tuple> result = queryFactory
             .select(member.count(),
                     member.age.sum(),
                     member.age.avg(),
                     member.age.max(),
                     member.age.min())
             .from(member)
             .fetch();
             
     Tuple tuple = result.get(0);
     
     assertThat(tuple.get(member.count())).isEqualTo(4);
     assertThat(tuple.get(member.age.sum())).isEqualTo(100);
     assertThat(tuple.get(member.age.avg())).isEqualTo(25);
     assertThat(tuple.get(member.age.max())).isEqualTo(40);
     assertThat(tuple.get(member.age.min())).isEqualTo(10);
}

ํ”„๋กœ์ ์…˜ ๋Œ€์ƒ์ด ๋‘˜ ์ด์ƒ์ผ ๊ฒฝ์šฐ ํŠœํ”Œ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • GroupBy
 @Test
 public void group() throws Exception {
 
     List<Tuple> result = queryFactory
             .select(team.name, member.age.avg())
             .from(member)
             .join(member.team, team)
             .groupBy(team.name)
             .fetch();
             
     Tuple teamA = result.get(0);
     Tuple teamB = result.get(1);
     
     assertThat(teamA.get(team.name)).isEqualTo("teamA");
     assertThat(teamA.get(member.age.avg())).isEqualTo(15);
     
     assertThat(teamB.get(team.name)).isEqualTo("teamB");
     assertThat(teamB.get(member.age.avg())).isEqualTo(35);
 }

๐Ÿ’ก having์„ ์ด์šฉํ•˜์—ฌ ๊ทธ๋ฃนํ™”๋œ ๊ฒฐ๊ณผ๋ฅผ ์ œํ•œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

    .groupBy(item.price)
    .having(item.price.gt(1000))

0๊ฐœ์˜ ๋Œ“๊ธ€

Powered by GraphCDN, the GraphQL CDN