์ผ๋ฐ˜์ ์œผ๋กœ Spring Boot ํ™˜๊ฒฝ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ, Spring Data JPA๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ CRUD  ๋ฉ”์†Œ๋“œ์™€ Named Query ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•ˆ๋˜๋Š” ๊ฒฝ์šฐ๋Š” JPQL์ด๋‚˜ Native Query๋กœ ์ž‘์„ฑํ•˜๊ฒŒ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ ๋กœ์ง์„ ๊ฐ€์ง„ ๊ฒฝ์šฐ์—๋Š” ๋ฉ”์†Œ๋“œ๋‚˜ ์ฟผ๋ฆฌ๋ฌธ์ด ๊ธธ์–ด์ ธ์„œ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ฒŒ ๋œ๋‹ค. ํŠนํžˆ Paging์ด๋‚˜ Sorting์„ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ, ๊ตฌํ˜„์ด ๋ณต์žกํ•ด์ง€๊ฒŒ ๋œ๋‹ค.

 

 

 

 

 

๐Ÿšจ ๋ฌธ์ œ ์ƒํ™ฉ

 ๋‚˜์˜ ๊ฒฝ์šฐ์—๋„ ์ด์ „ ํ”„๋กœ์ ํŠธ๊นŒ์ง€ Spring Data JPA๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” CRUD ๋ฉ”์†Œ๋“œ์™€ Named Query๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ–ˆ์—ˆ๊ณ , ์กฐ๊ธˆ ๋” ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์˜ ๊ฒฝ์šฐ์—๋Š” ๊ฐ€๋…์„ฑ์€ ์กฐ๊ธˆ์€ ๋–จ์–ด์ง€์ง€๋งŒ Native Query๋กœ ๋ณ„ ๋ฌธ์ œ์—†์ด ๊ตฌํ˜„์„ ํ–ˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ตœ๊ทผ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์ด ๋งŽ๊ณ , Paging๋„ ํ•˜๊ณ , Sorting๋„ ํ•˜๊ณ , ์ด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๊ฐœ์ˆ˜๊นŒ์ง€ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ–ˆ์—ˆ๋‹ค.

 

 

 ์ดˆ๊ธฐ ๊ตฌํ˜„์—์„œ๋Š” Paging๊ณผ Counting์ด ์—†์–ด์„œ, ๊ฐ€๋…์„ฑ์ด ์•ˆ ์ข‹์•˜์–ด๋„ named Query๋กœ Sorting์„ ๊ตฌํ˜„ํ–ˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ Paging๊ณผ Counting์ด ์ถ”๊ฐ€๋˜๋ฉด์„œ JpaRepository์—์„œ์˜ ๋ฉ”์†Œ๋“œ๋„ ๋ณต์žกํ•ด์ง€๊ธฐ ์‹œ์ž‘ํ–ˆ์œผ๋ฉฐ, ๊ทธ ์ฟผ๋ฆฌ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Service ๋‹จ์˜ ๋กœ์ง๋„ ๋ณต์žกํ•ด์ง€๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค. ์–ด์ฐŒ์–ด์ฐŒ ๊ตฌํ˜„์€ ํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ, ๋กœ์ง์ด ๋ณต์žกํ•˜๊ณ  ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด ๋น„ํšจ์œจ์ ์ด๊ณ  ๊ฐ๋‹นํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜์—ˆ๋‹ค. QueryDSL์„ ์ฒ˜์Œ ๋„์ž…ํ•ด๋ณด๋Š” ๊ฒƒ์ด๊ณ , ํŠนํžˆ Page ๊ฐ์ฒด๋„ ๋”ฐ๋กœ ์žˆ์–ด์„œ ์–ด๋ ค์›Œ๋ณด์˜€์ง€๋งŒ.. ๋” ๋Œ์•„์˜ฌ ์ˆ˜ ์—†๋Š” ๊ฐ•์„ ๊ฑด๋„ˆ๊ธฐ ์ „์— QueryDSL๋กœ ์ „ํ™˜ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ๋•๋ถ„์— QueryDSL์˜ ํ•„์š”์„ฑ๊ณผ ์ค‘์š”์„ฑ์„ ๋Š๋ผ๊ฒŒ ๋œ ๊ฒƒ ๊ฐ™๋‹ค!

 

 

 

 

 

QueryDSL ๋„์ž… ์ „ (+ Paging, Counting ๊ตฌํ˜„ ์ „)

Repository 

์ •๋ ฌ์€ ๊ตฌํ˜„๋œ ํŽ˜์ด์ง• ๋„์ž… ์ „ ์ฝ”๋“œ์ด๋‹ค.

public interface TicketRepository extends JpaRepository<Ticket, Long> {
	//priceRow
    List<Ticket> findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByPriceAsc(String departure, String destination, String date, int minPrice, int maxPrice);

	//depTimeEarly
    List<Ticket> findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByDepTimeAsc(String departure, String destination, String date, int minPrice, int maxPrice);

	//depTimeLate
    List<Ticket> findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByDepTimeDesc(String departure, String destination, String date, int minPrice, int maxPrice);

	//flightTimeRow
    List<Ticket> findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByTotalTimeNumAsc(String departure, String destination, String date, int minPrice, int maxPrice);
}

 

๊ฐ ์ •๋ ฌ ์กฐ๊ฑด ๋ณ„๋กœ Named Query๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์†Œ๋“œ๋“ค์„ ์ž‘์„ฑ์„ ํ–ˆ๋Š”๋ฐ ๊ต‰์žฅํžˆ ๊ธธ๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์ง€ ์•Š๋‹ค..

 

 

Service

Switch Case ๋ฌธ์œผ๋กœ ์ •๋ ฌ ์กฐ๊ฑด์— ๋”ฐ๋ผ ๊ตฌํ˜„๋œ Repository ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ–ˆ๋‹ค.

@Service
public class TicketServiceImpl implements TicketService {
    @Autowired
    TicketRepository ticketRepository;

    @Override
    public List<TicketRes> getTicketList(OneWayTicketReq ticketReq) {
       ...

        //๊ฒ€์ƒ‰
        if(direct[0]) { //๊ฒฝ์œ  ํ•„ํ„ฐ : ์ „์ฒด
            switch (sortType) {
                case "priceRow" : {
                    ticketList = ticketRepository.findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByPriceAsc(departure, destination, date, minPrice, maxPrice);
                    break;
                }
                case "gapHigh" : {
                	//๋‹น์‹œ ๊ตฌํ˜„์ค‘
                    break;
                }
                case "depTimeEarly" : {
                    ticketList = ticketRepository.findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByDepTimeAsc(departure, destination, date, minPrice, maxPrice);
                    break;
                }
                case "depTimeLate" : {
                    ticketList = ticketRepository.findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByDepTimeDesc(departure, destination, date, minPrice, maxPrice);
                    break;
                }
                case "flightTimeRow" : {
                    ticketList = ticketRepository.findByDepCodeAndArrCodeAndDepDateAndPriceBetweenOrderByTotalTimeNumAsc(departure, destination, date, minPrice, maxPrice);
                    break;
                }
            }
        } else {
            ...
        }

        //๊ฒฐ๊ณผ๊ฐ’ ๋ฐ˜ํ™˜
        ...

        return result;
    }

}

 

 

 

 

๐Ÿ”ง ์„ค์ •ํ•˜๊ธฐ

Gradle

 

Spring Data JPA์˜ ๊ฒฝ์šฐ, Gradle์— ํ•œ ์ค„๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

dependencies {
    ...
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	...
}

 

 

QueryDSL์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด ์ดˆ๊ธฐ ์„ค์ •์ด ๋ณต์žกํ•˜๋‹ค.

buildscript {
    ext {
        queryDslVersion = "5.0.0"
    }
}

plugins {
    ...
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}

...

dependencies {
    implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
    annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
    ...
}

def querydslDir = "$buildDir/generated/querydsl"
querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

...

 

 

 

 

 

QClass ์ƒ์„ฑํ•˜๊ธฐ

Gradle > other > compileQuerydsl ํด๋ฆญํ•œ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด ํ”„๋กœ์ ํŠธ์˜ build > generated > querydsl ํด๋”๊ฐ€ ์ƒ๊ธฐ๊ณ , Entity์˜ QClass๊ฐ€ ์ƒ์„ฑ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

 

Repository

public interface TicketRepository extends JpaRepository<Ticket, Long>, TicketRepositoryCustom {
}

 

๊ธฐ์กด์— JPA๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด Repository interface๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ์— ๊ธฐ๋ณธ Crud์™€ named Query๋ฅผ ์œ„ํ•œ JpaRepository๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์ง์ ‘ ๋งŒ๋“  RepositoryCustom์„ ์ƒ์†๋ฐ›๋Š”๋‹ค.

 

 

 

 

 

RepositoryCustom

public interface TicketRepositoryCustom {
    Page<Ticket> findAllTickets(Pageable pageable, String departure, String destination, String date, int minPrice, int maxPrice);
}

 

QueryDSL๋กœ ๊ตฌํ˜„ํ•  ๋ฉ”์†Œ๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ค€๋‹ค.

 

 

 

 

 

RepositoryCustomImpl

 

์˜ค๋ฒ„๋ผ์ด๋”ฉํ•œ ๋ฉ”์†Œ๋“œ ์•ˆ์— QueryDSL์„ ์ž‘์„ฑํ•ด์ค€๋‹ค.

public class TicketRepositoryImpl implements TicketRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    public TicketRepositoryImpl(EntityManager em){
        this.queryFactory = new JPAQueryFactory(em);
    }

    @Override
    public Page<Ticket> findAllTickets(Pageable pageable, String departure, String destination, String date, int minPrice, int maxPrice) {
        JPAQuery<Ticket> query  = queryFactory
                .selectFrom(ticket)
                .leftJoin(ticket.flightList, flight)
                .leftJoin(ticket.tendency, tendency)
                .where(ticket.depCode.eq(departure),
                        ticket.arrCode.eq(destination),
                        ticket.depDate.eq(date),
                        ticket.price.between(minPrice, maxPrice)
                )
                .distinct();

        sortAndOrder(query, pageable);

        List<Ticket> ticketList = query
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        Long totalCount =  queryFactory
                .select(ticket.countDistinct())
                .from(ticket)
                .leftJoin(ticket.flightList, flight)
                .leftJoin(ticket.tendency, tendency)
                .where(ticket.depCode.eq(departure),
                        ticket.arrCode.eq(destination),
                        ticket.depDate.eq(date),
                        ticket.price.between(minPrice, maxPrice)
                )
                .fetchOne();

        return new PageImpl<>(ticketList, pageable, totalCount);
    }
    
    private void sortAndOrder(JPAQuery<Ticket> query, Pageable pageable) {
        for (Sort.Order o : pageable.getSort()) {
            if(o.getProperty().equals("updown")) {
                PathBuilder pathBuilder = new PathBuilder(tendency.getType(), tendency.getMetadata());
                query.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC,
                        pathBuilder.get(o.getProperty())), new OrderSpecifier(Order.ASC,
                        ticket.depTime), new OrderSpecifier(Order.ASC,
                        ticket.arrTime));
            } else {
                PathBuilder pathBuilder = new PathBuilder(ticket.getType(), ticket.getMetadata());
                query.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC,
                        pathBuilder.get(o.getProperty())), new OrderSpecifier(Order.ASC,
                        ticket.depTime));
            }
        }
    }
    
}

 

 

 

 

 

 

๐Ÿ”Ž QueryDSL ๋ฉ”์†Œ๋“œ

  • select(ticket) : ticket์˜ ๋ชจ๋“  ์นผ๋Ÿผ์„ ์กฐํšŒํ•œ๋‹ค.
  • from(ticket) : ticket ํ…Œ์ด๋ธ”์—์„œ ๊ฐ€์ ธ์˜จ๋‹ค.
  • selectFrom() : select์™€ from์„ ํ•ฉ์นœ ๋ฉ”์†Œ๋“œ๋‹ค.
  • join(), leftJoin() : ๊ทธ๋ƒฅ join์‹œ innerJoin์ด ๋˜๊ณ , outer join์„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด left, right๋ฅผ ๋ถ™์—ฌ์ค˜์•ผ ํ•œ๋‹ค.
  • where() : ์กฐ๊ฑด์„ ๋„ฃ๋Š”๋‹ค
    • eq()
    • between(a, b)
  • distict() : ์ค‘๋ณต๋˜๋Š” ์นผ๋Ÿผ์„ ์ œ๊ฑฐํ•œ๋‹ค.
  • fetch() : query๋ฅผ ์ƒ์„ฑํ•ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

ํŽ˜์ด์ง•

  • offset() : ์–ป๊ณ ์ž ํ•˜๋Š” ๊ฒฐ๊ณผ์˜ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
  • limit() : ์ด ํŽ˜์ด์ง€ ์ˆ˜

์ •๋ ฌ

  • orderBy() : ์›ํ•˜๋Š” ์กฐ๊ฑด์— ๋”ฐ๋ผ ์ •๋ ฌํ•œ๋‹ค.

 

 

 

 

 

 

 

 

 

๐Ÿ“Œ ๊ฒฐ๊ณผ

 QueryDSL ๊ตฌํ˜„์„ ์œ„ํ•ด ์„ค์ •ํ•ด์•ผ ํ•  ๊ฒƒ๋“ค์ด ๋งŽ๊ณ , QueryDSL์˜ ๋ฉ”์†Œ๋“œ๋“ค๋„ ์ตํ˜€์•ผํ•ด์„œ ์ง„์ž…์žฅ๋ฒฝ์ด ์ข€ ์žˆ์—ˆ๋‹ค.

 

 ํ•˜์ง€๋งŒ Repository์—์„œ ๊ฐ์ข… named Query๋กœ ๋„˜์ฒ˜๋‚ฌ๋˜ ์ฝ”๋“œ์™€ Service ๋‹จ์—์„œ Switch ๋ฌธ์œผ๋กœ ๋„˜์ฒ˜๋‚˜๋˜ ์ฝ”๋“œ๊ฐ€ ์ค„์–ด๋“ค์–ด ํšจ์œจ์ ์ด๊ฒŒ ๋˜๊ณ , ๊ฐ€๋…์„ฑ๋„ ์ข‹์•„์ง€๊ฒŒ ๋˜์—ˆ๋‹ค!

 

๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์žฅ์ ์œผ๋กœ๋Š” ์ฟผ๋ฆฌ ์˜ค๋ฅ˜๋ฅผ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์žก์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. 

 

 

Repository

public interface TicketRepository extends JpaRepository<Ticket, Long>, TicketRepositoryCustom {
}

 

Service

@Service
public class TicketServiceImpl implements TicketService {
    @Autowired
    TicketRepository ticketRepository;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    OnewayTicketLikeRepository onewayTicketLikeRepository;

    @Autowired
    RoundTicketLikeRepository roundTicketLikeRepository;

    @Override
    public OnewayTicketListRes getOneWayTicketList(OnewayTicketReq ticketReq) {
        ...

        PageRequest pageRequest = PageRequest.of(page, 10, Sort.Direction.fromString(orderBy), sortType);

        //๊ฒ€์ƒ‰
        long totalCount = 0;

        List<Ticket> ticketList = new ArrayList<>();

        if(direct[0]) { //๊ฒฝ์œ  ํ•„ํ„ฐ : ์ „์ฒด
            Page<Ticket> result = ticketRepository.findAllTickets(pageRequest, departure, destination, date, minPrice, maxPrice);
            ticketList = result.getContent();
            totalCount += result.getTotalElements();
        }else {
        	...
        }

        //๊ฒฐ๊ณผ๊ฐ’ ๋ฐ˜ํ™˜
        ...

        return onewayTicketListRes;
    }
    
}

 

 

 

 

 

 

 

 

 

 

 

๐Ÿ“– ์ฐธ๊ณ  ์ž๋ฃŒ

https://velog.io/@jkijki12/Spring-QueryDSL-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

 

[Spring] QueryDSL ์™„๋ฒฝ ์ดํ•ดํ•˜๊ธฐ

QueryDsl !

velog.io

 

https://ksabs.tistory.com/186

 

[ClassFlix] EP 17. QueryDSL ๋„์ž… (ํŽ˜์ด์ง•, ์ •๋ ฌ)

Querydsl ๋„์ž… ๊ณ„ํš build.gradle ์ž‘์„ฑ, JPAQueryFactory๋“ฑ Querydsl ๋„์ž… ๊ธฐ์กด์— ์žˆ๋Š” jpql์„ querydsl๋กœ ๋ฐ”๊พธ๊ธฐ ํ™ˆ ํ™”๋ฉด์—์„œ ํŽ˜์ด์ง•, ์ •๋ ฌ ๊ธฐ๋Šฅ (๋Œ€๋Ÿ‰ ๊ฐ•์˜ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€, ddl ์„ค์ • ๋ณ€๊ฒฝ) ๊ฐ•์˜ ๊ฒ€์ƒ‰๊ธฐ๋Šฅ์ถ”๊ฐ€ (ํŽ˜์ด์ง•

ksabs.tistory.com

 

giraffe_