๐ŸŒ WEB/JPA

[Querydsl] ๋‚ด๊ฐ€ ์ฐพ์•„ ์“ฐ๋ ค๊ณ  ์ •๋ฆฌํ•œ ๊ธ€

์• ์ •์“ฐ 2022. 3. 15. 11:51

 

 

๐Ÿƒ‍โ™‚๏ธ ๊ณ„์†ํ•ด์„œ ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ ๐Ÿƒ‍โ™‚๏ธ 

๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ 2022/03/29

 

1. Querydsl ์„ ์™œ ์‚ฌ์šฉํ• ๊นŒ?

2. ์ž‘๋™๋ฐฉ์‹?

3. ์˜์กด์„ฑ

4. Repository ๊ตฌ์กฐ

5. Projection ์˜ ๋Œ€ํ•œ ๊ณ ์ฐฐ ...

6. ๋™์ ์ฟผ๋ฆฌ (BooleanBuilder)

7. ExpressionUtils

8. ์ •๋ ฌ ํƒ€์ž…์— ๋”ฐ๋ฅธ ์ •๋ ฌ ์ฒ˜๋ฆฌ

 

 

 

Querydsl ์„ ์™œ ์‚ฌ์šฉํ• ๊นŒ?

 

JPA ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ (@Query ํฌํ•จ) ์กฐํšŒ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ํ•œ๊ณ„๊ฐ€ ์žˆ๋‹ค.

๋™์ ์ธ ์ฟผ๋ฆฌ์ธ ๊ฒฝ์šฐ์ธ๋ฐ ์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ๋ฌธ ํŽ˜์ด์ง€๋ฅผ ๊ฒ€์ƒ‰ ํ•œ๋‹ค๊ณ  ํ–ˆ์„ ๋•Œ ์นดํ…Œ๊ณ ๋ฆฌ or ์ƒํ’ˆ๋ช… or ๊ธฐ์—…๋ช… ๋“ฑ๋“ฑ... ์œผ๋กœ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์ด ๋‹ฌ๋ผ์ง€๋Š” ๋ถ€๋ถ„์ด๋‹ค.

 

๊ทธ๋ž˜์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ ๊ฒƒ์ด ๋ฐ”๋กœ Querydsl ํ”„๋ ˆ์ž„์›Œํฌ ์ด๋‹ค.

- ํƒ€์ž… ์ฒดํฌ๊ฐ€ ๋ฐ”๋กœ ๊ฐ€๋Šฅํ•˜๋‹ค

- ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค

 

์ž‘๋™๋ฐฉ์‹?

Querydsl -> JPQL -> SQL 

 

 

์˜์กด์„ฑ

    <dependency>
      <groupId>com.querydsl</groupId>
      <artifactId>querydsl-jpa</artifactId>
    </dependency>

    <dependency>
      <groupId>com.querydsl</groupId>
      <artifactId>querydsl-apt</artifactId>
    </dependency>
    
    <plugin>
        <groupId>com.mysema.maven</groupId>
        <artifactId>apt-maven-plugin</artifactId>
        <version>1.1.3</version>
        <executions>
          <execution>
            <goals>
              <goal>process</goal>
            </goals>
            <configuration>
              <outputDirectory>target/generated-sources/java</outputDirectory>
              <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
          </execution>
        </executions>
      </plugin>

์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Q ๊ฐ€ ๋ถ™๋Š” ํด๋ž˜์Šค๋“ค์„ ์ž๋™ ์ƒ์„ฑํ•ด์ค€๋‹ค.

๋นŒ๋“œ๋ฅผ ํ•ด๋ณด๋ฉด target/generated-sources/java ์—ฌ๊ธฐ์— Qํด๋ž˜์Šค๋“ค์ด ์ƒ๊ฒจ๋‚œ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

- querydsl-jpa : QueryDSL JPA ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

- querydsl-apt : ์ฟผ๋ฆฌ ํƒ€์ž…(Q)์„ ์ƒ์„ฑํ•  ๋•Œ ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

 

 

 

Repository ๊ตฌ์กฐ

public interface UserRepository extends JpaRepository<Users, Long>, UserRepositoryCustom {
}
public interface UserRepositoryCustom {
  List<Users> findByOrderByUser(Long userId, UserSearch userSearch);
}

 

public class UserRepositoryImpl extends QuerydslRepositorySupport implements
  UserRepositoryCustom {

  private JPAQueryFactory queryFactory;
	// 1.
  public UserRepositoryImpl() {
    super(Users.class);
  }
	// 2.
  @Override
  public void setEntityManager(EntityManager entityManager) {
    super.setEntityManager(entityManager);
    queryFactory = new JPAQueryFactory(entityManager);
  }

 

1. QuerydslRepositorySupport๋ฅผ ์ƒ์†ํ•˜๊ฒŒ๋˜๋ฉด ๋” ์ด์ƒ QueryDsl์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋‹จ, ์ƒ์„ฑ์ž์—์„œ super(Entity.class)๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๊ฐ€๋Šฅ ํ•˜๋‹ค.

 

- QuerydslRepositorySupport ํด๋ž˜์Šค์—๋Š” ๊ธฐ๋ณธ์ƒ์„ฑ์ž๊ฐ€ ์—†์Œ.

 

2.

QuerydslRepositorySupport ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ตฌํ˜„์ฒด์˜ JPQLQuery ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ฟผ๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— from ์œผ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ queryFactory (์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” select ๋กœ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด )๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด 

EntityManager ๋ฅผ ์ƒ์œ„ ํด๋ž˜์Šค์— ์ „๋‹ฌํ•˜์—ฌ JPAQuery์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” (select,selectFrom ๋“ฑ๋“ฑ ..) ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

-> ์„ฑ๋Šฅ ์ฐจ์ด๋Š” ์—†์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” SQL๋กœ ๊ตฌํ˜„ํ•ด์„œ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋Š” ๊ฒƒ ๋˜ํ•œ ์ด๋“์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค!

 

 

Projection ์˜ ๋Œ€ํ•œ ๊ณ ์ฐฐ ...

 

์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜๋‚˜์˜ ํ•„๋“œ๋ฅผ ์กฐํšŒ ํ•˜๋Š” ๊ฒƒ์€ ์‰ฝ๋‹ค.

List<String> result = queryFactory
		.select(users.name)
		.from(users)
		.fetch();

ํ•˜์ง€๋งŒ ๋ณดํ†ต ์—ฌ๋Ÿฌ ๋ฆฌํ„ด๊ฐ’์ด ํ•„์š”ํ•œ๋ฐ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•œ๋‹ค๋ฉด Tuple ๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค.

์šฐ๋ฆฌ๋Š” ๊ฒฐ๊ณผ ๊ฐ’์„ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— DTO ๋กœ ๋งŒ๋“ค์–ด ์“ฐ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ณ ์ž ํ•œ๋‹ค!

List<Tuple> result = queryFactory
		.select(users.name,user.age)
		.from(users)
		.fetch();

 

Projections ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

1. constructor

์ƒ์„ฑ์ž ๊ธฐ๋ฐ˜ ์ƒ์„ฑ

-> ๋ถˆ๋ณ€ ๊ฐ์ฒด ์ด์ง€๋งŒ dto ์ƒ์„ฑ์ž์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ์™€ ๋™์ผํ•ด์•ผ ํ•œ๋‹ค.(ํ•„๋“œ๊ฐ€ ๋งŽ์„์ˆ˜๋ก ์‹ค์ˆ˜ ์œ„ํ—˜์ด ์žˆ๋‹ค.)

-> ์ด๋ฆ„์„ ๋งž์ถฐ์ฃผ์ง€ ์•Š์•„๋„ ํ•ด๋‹น์ž๋ฆฌ์— ๋“ค์–ด๊ฐ€๋Š” ๊ฐ’์„ ๋„ฃ์–ด์ค€๋‹ค

List<UserInfoDto> result = queryFactory
	.select(
        Projections.constructor(UserInfoDto.class,
        users.name,
        user.age
        ))
	.from(users)
	.fetch();

2. bean

DTO์— ์žˆ๋Š” setter ๋ฉ”์„œ๋“œ(@Setter) ๊ธฐ๋ฐ˜ ์ƒ์„ฑ

-> DTO๊ฐ€ ๋ถˆ๋ณ€ ๊ฐ์ฒด๊ฐ€ ์•„๋‹ˆ๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ง€์–‘ํ•˜๋Š” ๋ฐฉ๋ฒ•

List<UserInfoDto> result = queryFactory
	.select(
        Projections.bean(UserInfoDto.class,
        users.name.as("userName"),
        user.age.as("userAge")
        ))
	.from(users)
	.fetch();

3. Fields

ํ•„๋“œ์— ์ง์ ‘ ์ ‘๊ทผํ•ด์„œ ๊ฐ’์„ ์ฑ„์›Œ์ค€๋‹ค.

-> ํ•„๋“œ์— ์ง์ ‘ ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฆ„์ด ํ‹€๋ฆฌ๋ฉด ์•ˆ๋œ๋‹ค

-> DB ์ปฌ๋Ÿผ ์ด๋ฆ„๊ณผ DTO ํ•„๋“œ ์ด๋ฆ„์ด ๋‹ค๋ฅผ ๊ฒฝ์šฐ .as ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค

List<UserInfoDto> result = queryFactory
	.select(
        Projections.fields(UserInfoDto.class,
        users.name.as("userName"),
        user.age.as("userAge")
        ))
	.from(users)
	.fetch();

4. @QueryProjection

DTO ์— ๋Œ€ํ•œ Q class ๊ธฐ๋ฐ˜ ์ƒ์„ฑ

-> DTO ์ƒ์„ฑ์ž์— @QueryProjection ์–ด๋…ธํ…Œ์ด์…˜์„ ์„ ์–ธํ•˜๊ณ  querydsl ๋ฅผ ์ปดํŒŒ์ผ ํ•˜๋ฉด QUserInfoDto.class ์ฒ˜๋Ÿผ Q ํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

-> Q ํด๋ž˜์Šค ์ƒ์„ฑ์„ ๋ฏธ๋ฆฌ ํ•ด์•ผ ํ•œ๋‹ค.

-> DTO ๊ฐ€ querydsl ์— ์˜์กดํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

List<UserInfoDto> result = queryFactory
	.select(
        new QUserInfoDto(
        users.name.as("userName"),
        user.age.as("userAge")
        ))
	.from(users)
	.fetch();

 

๋‚˜๋Š” ํ•„๋“œ๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ• ๊ฒƒ ๊ฐ™๋‹ค. 

1. ์ƒ์„ฑ์ž - ์‹ค๋ฌด์—์„œ๋Š” ์ ˆ๋•Œ ์‚ฌ์šฉ ๋ชปํ• ๊ฒƒ ๊ฐ™๋‹ค dto ํ•„๋“œ๊ฐ€ 10๊ฐœ ์ด์ƒ ์ผ ๋•Œ๊ฐ€ ๋งŽ๋‹ค

2. setter -  ๋ถˆ๋ณ€์„ฑ ๊ฐ์ฒด๊ฐ€ ์•„๋‹๋•Œ ๋ถˆ์•ˆํ•˜๋‹ค

3. QueryProjection - Qํด๋ž˜์Šค๊ฐ€ ์ถ”๊ฐ€๋œ๋‹ค๋Š”๊ฒŒ ๋ง˜์— ์•ˆ๋“ค๊ณ  querydsl ์— ์˜์กด์ด ๊ฐ•ํ•ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์œ ์ง€๋ณด์ˆ˜์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

 

๋™์ ์ฟผ๋ฆฌ (BooleanBuilder)

๊ฒ€์ƒ‰ ๋“ฑ์—์„œ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋„๋ก ์ฟผ๋ฆฌ๋ฌธ์— ์ž‘์„ฑ๋˜๋Š” ์กฐ๊ฑด๋“ค์„ nullable ํ•˜๊ฒŒ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

 

1. ์œ ์ €๋ฅผ ๊ฒ€์ƒ‰ํ•  ๋•Œ ์ด๋ฆ„์ด๋‚˜ ๋‚˜์ด๋กœ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์‹ถ๋‹ค

2. ์ด๋ฆ„ํ•„๋“œ๋กœ ๊ฐ’์„ ๋ฐ›์€ ๊ฒฝ์šฐ builder.and(users.name.eq(name)) ์ฟผ๋ฆฌ๊ฐ€ ์ž‘๋™ํ•œ๋‹ค.

    BooleanBuilder builder = new BooleanBuilder();
    
    if (name != null) {
      builder.and(users.name.eq(name));
    }
    if (age != null) {
      builder.and(users.age.eq(age));
    }
    
    return jpaQueryFactory
            .selectFrom(users)
            .where(builder)
            .fetch();

 

์‹ค๋ฌด์—์„œ๋Š” ์ข€๋” ๊ฐ€๋…์„ฑ์„ ์ข‹๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ์”๋‹ˆ๋‹ค

return jpaQueryFactory
            .selectFrom(users)
            .where(eqName(name), eqAge(age))
            .fetch();
  }
 
  private BooleanExpression eqName(String name) {
    return name != null ? users.name.eq(name) : null;
  }
 
  private BooleanExpression eqAge(int age) {
    return age == null ? users.age.eq(age) : null;
  }

 

ExpressionUtils

http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/core/types/ExpressionUtils.html

 

ExpressionUtils (Querydsl 4.4.0 API)

Converts the given object to an Expression Casts expressions and wraps everything else into co

querydsl.com

๊ณต์‹ ๋ฌธ์„œ์— ๋ณด๋ฉด ์–ด๋–ค ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค!

ํšŒ์‚ฌ์—์„œ ๋ ˆ๊ฑฐ์‹œ ํ”„๋กœ์ ํŠธ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์„ ๋ดค๋Š”๋ฐ ExpressionUtils ๋กœ Subquery ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๊ฑธ ๋ณด๊ณ  ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค!

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์—”ํ‹ฐํ‹ฐ๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ธŒ์ฟผ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ์ผ์€ ๊ฑฐ์˜ ์—†์—ˆ๋‹ค!

 

return jpaQueryFactory
            .select(Projections.fields(
            ExpressionUtils.as(getUserCount(UserStatus.ํƒˆํ‡ดํšŒ์›),"usersCount"))
            .from(users).fetchCount());
            
            


private JPQLQuery<Long> getUserCount(UserStatus status){
	return	select(users.id.count())
		.from(users)
		.where(status == null ? null : eqUserStatus(status))
}

private Predicate eqUserStatus(UserStatus status){
	return users.status.eq(status);
}

 

์—ฌ๊ธฐ์„œ as ๋กœ ์•Œ๋ฆฌ์•„์Šค๋ฅผ ํ•ด์ฃผ๋Š”๋ฐ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” Expression ์€ ์•„๋ž˜์™€ ๊ฐ™์€ ํƒ€์ž…์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ JPQLQuery ๋ฅผ ๋งŒ๋“ค์–ด์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋Œ€์ž… ํ•ด์ฃผ์—ˆ๋‹ค!

Predicate ๋Š” ์ž๋ฐ”์ฝ”๋“œ๋กœ ์กฐ๊ฑด๋ฌธ์„ ํ‘œํ˜„(where ์ ˆ)ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์–ด์„œ ์กฐ๊ฑด๋ฌธ๋“ค์„ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค~! 

 

 http://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/core/types/Expression.html

 

Expression (Querydsl 4.4.0 API)

Expression defines a general typed expression in a Query instance. The generic type parameter is a reference to the type the expression is bound to. The central Expression subinterfaces are

querydsl.com

 

 

 

 

์ •๋ ฌ ํƒ€์ž…์— ๋”ฐ๋ฅธ ์ •๋ ฌ ์ฒ˜๋ฆฌ

 

private OrderSpecifier getOrderBy(Order sortType) {
  switch (sortType) {
    case ์ตœ๊ทผ๊ฐ€์ž…:
      return new OrderSpecifier(Order.DESC, user.createAt);
    case ๋†’์€์—ฐ๋ น์ˆœ:
      return new OrderSpecifier(Order.ASC, user.age);
    case ํšŒ์›์ƒํƒœ:
      return new OrderSpecifier(Order.ASC, user.status);
    default:
      return new OrderSpecifier(Order.ASC, user.createAt);
  }
}

 

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.querydsl.core.types;

public enum Order {
  ASC,
  DESC;

  private Order() {
  }
}
๋ฐ˜์‘ํ˜•