Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 스프링부트핵심가이드
- /etc/network/interfaces
- 구멍가게코딩단
- 코드로배우는스프링웹프로젝트
- network configuration
- resttemplate
- 목록처리
- 처음 만나는 AI 수학 with Python
- Kernighan의 C언어 프로그래밍
- ㅒ
- 서버설정
- GIT
- 리눅스
- 네트워크 설정
- 이터레이터
- 데비안
- 페이징
- d
- 선형대수
- 처음 만나는 AI수학 with Python
- baeldung
- 티스토리 쿠키 삭제
- 자료구조와 함께 배우는 알고리즘 입문
- 자바편
- iterator
- 알파회계
- 친절한SQL튜닝
- 스프링 시큐리티
- 자료구조와함께배우는알고리즘입문
- 코드로배우는스프링부트웹프로젝트
Archives
- Today
- Total
bright jazz music
[bootBoard] N:1(다대일) 연관관계: 11-6. JPQLQuery로 Page<Object[]> 처리: sort 처리 / count 처리 본문
Framework/Spring
[bootBoard] N:1(다대일) 연관관계: 11-6. JPQLQuery로 Page<Object[]> 처리: sort 처리 / count 처리
bright jazz music 2022. 10. 11. 23:08- pageable의 Sort 객체는 JPQLQuery의 orderBy()의 파라미터로 전달되어야 하지만 JPQL에서는 Sort 객체를 지원하지 않는다
- 따라서 orderBy()의 경우 OrderSpecifier<T extends Comparable>을 파라미터로 처리해야 한다
- OrderSpecifier(Order order, Expression<T> target)
- OrderSpecifier의 Order는 com.querydsl.coreTypes.Order 타입이다.
- Expression은 com.querydsl.core.Expression이다.
1. SearchBoardRepository 수정: sort / count 처리를 위해
추가되는 코드
//SearchBoardRepositoryImpl.java
///...
//Order by : sort
Sort sort = pageable.getSort();
//tuple.orderBy(board.bno.desc());
sort.stream().forEach(order -> {
Order direction = order.isAscending() ? Order.ASC : Order.DESC;
String prop = order.getProperty();
PathBuilder orderByExpression = new PathBuilder(Board.class, "board");
tuple.orderBy(new OrderSpecifier(direction, orderByExpression.get(prop)));
});
///...
아래는 추가된 코드를 비롯한 전체 코드. import에 주의할 것.
//SearchBoardRepository.java
package com.example.bootboard.repository.search;
import com.example.bootboard.entity.*;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import java.util.List;
import java.util.stream.Collectors;
import static com.example.bootboard.entity.QBoard.board;
@Log4j2
public class SearchBoardRepositoryImpl extends QuerydslRepositorySupport
implements SearchBoardRepository {
//생성자
public SearchBoardRepositoryImpl(){
super(Board.class); //여기서 super는 querydslRepositorySupport이다.
}
@Override
public Board search1() {
log.info("search1............");
//JPQL 사용
//이제 로그가 찍힐 뿐만 아니라 아래의 JPQL도 실행된다.
QBoard board = QBoard.board;
QReply reply = QReply.reply;
QMember member = QMember.member;
JPQLQuery<Board> jpqlQuery = from(board);
jpqlQuery.leftJoin(member).on(board.writer.eq(member));
jpqlQuery.leftJoin(reply).on(reply.board.eq(board));
//집합함수 처리 : groupBy()
//튜플 사용!!!!!
JPQLQuery<Tuple> tuple = jpqlQuery.select(board, member.email, reply.count());
tuple.groupBy(board);
// jpqlQuery.select(board, member.email, reply.count())
// .groupBy(board);
log.info("------------------------------");
log.info(tuple); //jpqlQuery -> tuple
log.info("------------------------------");
List<Tuple> result = tuple.fetch();
// List<Board> result = jpqlQuery.fetch();
log.info(result);
return null;
}
// 조인 처리
// QBoard board = QBoard.board;
// QReply reply = QReply.reply;
//
// JPQLQuery<Board> jpqlQuery = from(board);
// jpqlQuery.leftJoin(reply).on(reply.board.eq(board));
//
// List<Board> result = jpqlQuery.fetch();
// return null;
// }
// //Q도메인의 QBoard
// QBoard board = QBoard.board;
// JPQLQuery<Board> jpqlQuery = from(board);
// jpqlQuery.select(board).where(board.bno.eq(3L)); //bno가 3번인 데이터를 select
//
// log.info("------------------------------");
// log.info(jpqlQuery);
// log.info("------------------------------");
//
// List<Board> result = jpqlQuery.fetch();
// return null;
// }
@Override
public Page<Object[]> searchPage(String type, String keyword, Pageable pageable) {
log.info("searchPage............................");
QBoard board = QBoard.board;
QMember member = QMember.member;
QReply reply = QReply.reply;
JPQLQuery<Board> jpqlQuery = from(board);
jpqlQuery.leftJoin(member).on(board.writer.eq(member));
jpqlQuery.leftJoin(reply).on(reply.board.eq(board));
//SELECT b, w, count(r) FROM Board b
//LEFT JOIN b.writer w LEFT JOIN Reply r ON r.board = b
JPQLQuery<Tuple> tuple = jpqlQuery.select(board, member, reply.count());
BooleanBuilder booleanBuilder = new BooleanBuilder();
BooleanExpression expression = board.bno.gt(0L);
booleanBuilder.and(expression);
if (type != null) {
String[] typeArr = type.split("");
//검색 조건 작성
BooleanBuilder conditionBuilder = new BooleanBuilder();
for (String t : typeArr) {
switch (t) {
case "t":
conditionBuilder.or(board.title.contains(keyword));
break;
case "w":
conditionBuilder.or(member.email.contains(keyword));
break;
case "c":
conditionBuilder.or(board.content.contains(keyword));
break;
}
}
booleanBuilder.and(conditionBuilder);
}
tuple.where(booleanBuilder);
/////////////////////////////////////////////////////////////////
//Order by : sort
Sort sort = pageable.getSort();
//tuple.orderBy(board.bno.desc());
sort.stream().forEach(order -> {
Order direction = order.isAscending() ? Order.ASC : Order.DESC;
String prop = order.getProperty();
PathBuilder orderByExpression = new PathBuilder(Board.class, "board");
tuple.orderBy(new OrderSpecifier(direction, orderByExpression.get(prop)));
});
tuple.groupBy(board);
//page처리
tuple.offset(pageable.getOffset());
tuple.limit(pageable.getPageSize());
List<Tuple> result = tuple.fetch();
log.info(result);
//count얻기
long count = tuple.fetchCount();
log.info("COUNT: " + count);
return new PageImpl<Object[]>(
result.stream().map(t -> t.toArray())
.collect(Collectors.toList()), pageable, count);
}
}
- org.springframework.data.domain.Sort는 내부적으로 여러 개의 Sort 객체를 연결할 수 있기 때문에 forEach()를 이용해서 처리한다.
- OrderSpecifier에는 정렬이 필요하므로 Sort 객체의 정렬 관련 정보를 com.querydsl.core.types.Order 타입으로 처리하고 Sort 객체에 속성(bno, title) 등은 PathBuilder를 사용해서 처리한다.
- PathBuilder를 생성할 때 문자열로 된 이름은 JPQLQuery를 생성할 때 이용하는 변수명과 동일해야 한다.
- JPQLQuery를 이용해 동적으로 검색조건을 처리하는 과정을 복잡하다. 그러나 한 번의 개발만으로 count쿼리도 같이 처리할 수 있는 것이 장점이다.
- count를 얻는 방법은 fetchCount()를 이용하면 된다.
//count얻기
long count = tuple.fetchCount();
log.info("COUNT: " + count);
Pageable을 파라미터로 전달받은 이유는 JPQLQuery의 offset()과 limit()를 사용해서 페이지 처리를 진행하기 위해서이다. ("//Page처리" 부분)
//page처리
tuple.offset(pageable.getOffset());
tuple.limit(pageable.getPageSize());
- SearchBoardRepositoryImpl 클래스의 searchPage()의 리턴 타입은 Page<Object[]>타입이다. 따라서 메서드 내부에서 Page타입의 객체를 생성해야 한다.
- Page는 인터페이스 타입이므로 실제 객체는 org.springframework.data.domain.PageImpl 클래스를 사용하여 생성한다.
- PageImpl 클래스의 생성자에는 Pageable과 long 값을 이용하는 생성자가 존재한다.
return new PageImpl<Object[]>(
result.stream().map(t -> t.toArray())
.collect(Collectors.toList()), pageable, count);
2. 테스트
Page<Object[]> result = boardRepository.searchPage("t", "3", pageable); //책에선 1, 난 3을 넣었음
//BoardRepositoryTests.java
@SpringBootTest
public class BoardRepositoryTests {
@Autowired
private BoardRepository boardRepository;
///...
//여기서 테스트
@Test
public void testSearchPage2(){
Pageable pageable =
PageRequest.of(0, 10,
Sort.by("bno").descending()
.and(Sort.by("title").ascending()));
Page<Object[]> result = boardRepository.searchPage("t", "3", pageable); //책에선 1, 난 3을 넣었음
}
///...
}
/*
* testInsert()는 한 명의 사용자가 하나의 게시물을 등록하도록 작성되었다.
* */
결과
콘솔 로그
테스트는 고의적으로 중첩되는 Sort 조건을 만들어서 추가하였다.
order by 조건이 만들어진 것과, 목록을 위한 SQL과 count처리를 위한 SQL이 실행되는 것을 확인할 수 있다.
2022-10-12 09:07:09.157 INFO 9560 --- [ Test worker] c.e.b.repository.BoardRepositoryTests : Started BoardRepositoryTests in 3.978 seconds (JVM running for 5.442)
2022-10-12 09:07:09.331 INFO 9560 --- [ Test worker] c.e.b.r.s.SearchBoardRepositoryImpl : searchPage............................
Hibernate:
select
board0_.bno as col_0_0_,
member1_.email as col_1_0_,
count(reply2_.rno) as col_2_0_,
board0_.bno as bno1_0_0_,
member1_.email as email1_1_1_,
board0_.moddate as moddate2_0_0_,
board0_.reg_date as reg_date3_0_0_,
board0_.content as content4_0_0_,
board0_.title as title5_0_0_,
board0_.writer_email as writer_e6_0_0_,
member1_.moddate as moddate2_1_1_,
member1_.reg_date as reg_date3_1_1_,
member1_.name as name4_1_1_,
member1_.password as password5_1_1_
from
board board0_
left outer join
member member1_
on (
board0_.writer_email=member1_.email
)
left outer join
reply reply2_
on (
reply2_.board_bno=board0_.bno
)
where
board0_.bno>?
and (
board0_.title like ? escape '!'
)
group by
board0_.bno
order by
board0_.bno desc,
board0_.title asc limit ?
2022-10-12 09:07:09.448 INFO 9560 --- [ Test worker] c.e.b.r.s.SearchBoardRepositoryImpl : [[Board(bno=95, title=Title...93, content=Content...93), Member(email=user93@aaa.com, password=11111, name=USER93), 0], [Board(bno=85, title=Title...83, content=Content...83), Member(email=user83@aaa.com, password=11111, name=USER83), 0], [Board(bno=75, title=Title...73, content=Content...73), Member(email=user73@aaa.com, password=11111, name=USER73), 1], [Board(bno=65, title=Title...63, content=Content...63), Member(email=user63@aaa.com, password=11111, name=USER63), 0], [Board(bno=55, title=Title...53, content=Content...53), Member(email=user53@aaa.com, password=11111, name=USER53), 0], [Board(bno=45, title=Title...43, content=Content...43), Member(email=user43@aaa.com, password=11111, name=USER43), 0], [Board(bno=41, title=Title...39, content=Content...39), Member(email=user39@aaa.com, password=11111, name=USER39), 0], [Board(bno=40, title=Title...38, content=Content...38), Member(email=user38@aaa.com, password=11111, name=USER38), 1], [Board(bno=39, title=Title...37, content=Content...37), Member(email=user37@aaa.com, password=11111, name=USER37), 0], [Board(bno=38, title=Title...36, content=Content...36), Member(email=user36@aaa.com, password=11111, name=USER36), 0]]
Hibernate:
select
count(distinct board0_.bno) as col_0_0_
from
board board0_
left outer join
member member1_
on (
board0_.writer_email=member1_.email
)
left outer join
reply reply2_
on (
reply2_.board_bno=board0_.bno
)
where
board0_.bno>?
and (
board0_.title like ? escape '!'
)
2022-10-12 09:07:09.458 INFO 9560 --- [ Test worker] c.e.b.r.s.SearchBoardRepositoryImpl : COUNT: 19
2022-10-12 09:07:09.493 INFO 9560 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-10-12 09:07:09.496 INFO 9560 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-10-12 09:07:09.504 INFO 9560 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
See https://docs.gradle.org/7.5/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 7s
6 actionable tasks: 3 executed, 3 up-to-date
오전 9:07:09: Task execution finished ':test --tests "com.example.bootboard.repository.BoardRepositoryTests.testSearchPage2"'.
'Framework > Spring' 카테고리의 다른 글
RestTemplate 01 : 파라미터 없는 GET 요청 (0) | 2023.03.02 |
---|---|
[bootBoard] N:1(다대일) 연관관계: 11-7. 목록화면에서 검색 처리 (0) | 2022.10.12 |
[bootBoard] N:1(다대일) 연관관계: 11-5. JPQLQuery로 Page<Object[]> 처리: 검색 조건의 처리 (0) | 2022.10.11 |
[bootBoard] N:1(다대일) 연관관계: 11-4. JPQLQuery로 Page<Object[]> 처리 (0) | 2022.10.11 |
[bootBoard] N:1(다대일) 연관관계: 11-3. Tuple객체, groupBy()를 사용한 집합 함수 처리 (0) | 2022.10.11 |
Comments