티스토리 뷰
일반적인 jpa paging 구현방법이라면 간단한 구글링 만으로도 방법을 알 수가 있다.
하지만, 일반적이지 않은 상황에서는 QuerydslRepositorySupport를 상속받아서 구현하는것이 아니라
직접 구현해야하는 경우가 발생한다.
아래 경우는, rownum을 동반한 페이징을 직접 구현하여 사용하고자 했던 기록이다.
** 2021. 02. 25 **
(현재는 Page로 결과만 전달받고, rownum util을 사용하지 않으며 프론트엔드 단에서 Page의 변수를 계산하여 사용중이다)
계산식은 아래와 같다.
<ul v-for="(item, index) in data.content">
<li class="voice_font_statistics_rank">
{{(data.totalElements - data.numberOfElements) * data.number + index + 1}}
</li>
</ul
data = 서버측에서 내려받은 Page클래스
index = foreach index
(전체 count - 현재 페이지의 길이) * 현재 페이지 넘버 + 인덱스 + 1
(1을 더해주는 이유는 인덱스가 0부터 시작하기 때문)
굳이 아래와 같이 rownum util을 추가하여 사용하려고 하지 말자.
** 2021. 02. 25 **
방법1. PageImpl을 통한 Page 구현 (참고 : https://mycup.tistory.com/186)
방법1은 현재 페이지와 페이지 출력수를 인자로 받아 offset과 limit를 계산해주는 방식이다.
먼저, 함수를 생성해주자.
(필자는 공통 repository에 default method로 생성하여 이를 상속받아 사용중이다.)
/**
* 쿼리 페이징
* @param cquery
* @param query
* @param pageable
* @return
*/
default Page pagination(JPAQuery cquery, JPAQuery query, Pageable pageable){
Long total = cquery.fetchCount();
query.offset(pageable.getOffset());
query.limit(pageable.getPageSize());
List content = total > pageable.getOffset() ? query.fetch() : Collections.emptyList();
Page<DefaultVO> page = new PageImpl(content, pageable, total);
RownumUtil rownumUtil = new RownumUtil(page.getTotalElements(),
pageable.getPageNumber()+1,
pageable.getPageSize());
page.getContent().forEach(v->v.setrNum(rownumUtil.getRowNum()));
return page;
}
매개변수 중 cquery는 전체 페이지 수 출력을 위한 query,
query는 실제 offset과 limit가 이루어지는 query,
pageable은 PageRequest.of()를 통해 전달받을 현재페이지와 페이지출력수 정보이다.
(RownumUtil관련 부분과 rNum에 값을 주입하는 부분은 rownum 출력을 위한 과정이다. 이 과정을 위해서는 제너릭클래스가 필요하며, 해당 클래스에 getter/setter가 있어야 한다.)
(RownumUtil은 아래에 후술되어있으니, 필요하댜면 해당 클래스 생성 후 사용하기 바란다.)
그 다음, 아래와 같이 전체 페이지 수 출력, 실제 페이징 출력 쿼리를 작성하자.
/**
* 게시글 목록 쿼리
* @param entityManager
* @param ta
* @return
*/
default JPAQuery getArticleListQuery(EntityManager entityManager, TB_ARTICLES ta) {
return new JPAQuery<TB_ARTICLES>(entityManager)
.select(
Projections.bean(
tb_articles,
tb_articles.articleSeq,
tb_articles.bbsInfoSeq,
Projections.bean(
tb_bbs_category,
tb_bbs_category.categoryCd,
tb_bbs_category.categoryNm
).as("tb_bbs_category"),
tb_articles.subject,
tb_articles.delYn,
tb_articles.secretYn,
tb_articles.noticeYn,
tb_articles.userId,
tb_articles.prtSeq,
tb_articles.prtGrp,
tb_articles.bbsOrder,
tb_articles.inptIp,
tb_articles.updIp,
tb_articles.readCnt,
tb_articles.inptUserName,
tb_articles.inptDate,
tb_articles.updUserName,
tb_articles.updDate,
ExpressionUtils.as(
new JPAQuery<TB_ARTICLE_COMMENTS>(entityManager)
.select(tb_article_comments.commentSeq.count())
.from(tb_article_comments)
.where(tb_article_comments.articleSeq.eq(tb_articles.articleSeq).and(tb_article_comments.delYn.eq("N"))),
"comment_cnt"
),
ExpressionUtils.as(
new JPAQuery<TB_FILES>(entityManager)
.select(tb_files.fileSeq.count())
.from(tb_files)
.where(tb_files.tableSeq.eq(tb_articles.articleSeq).and(tb_files.tableNm.eq("TB_ARTICLES"))),
"file_cnt"
),
Projections.bean(
tb_bbs_info,
tb_bbs_info.bbsCd,
tb_bbs_info.bbsNm
).as("tb_bbs_info")
)
).from(tb_articles)
.innerJoin(tb_bbs_info)
.on(tb_bbs_info.bbsInfoSeq.eq(tb_articles.bbsInfoSeq))
.leftJoin(tb_bbs_category)
.on(tb_bbs_category.bbsInfoSeq.eq(tb_articles.bbsInfoSeq).and(tb_bbs_category.categoryCd.eq(tb_articles.categoryCd)))
.where(
ta.getBbsInfoSeq() == 0 ?
articleWhere(tb_articles, ta) :
tb_articles.bbsInfoSeq.eq(ta.getBbsInfoSeq())
.and(articleWhere(tb_articles, ta)
)
)
.orderBy(tb_articles.prtGrp.desc(), tb_articles.bbsOrder.asc().nullsFirst());
}
/**
* 게시글 TOTAL COUNT 쿼리
* @param entityManager
* @param ta
* @return
*/
default JPAQuery getArticleListCNTQuery(EntityManager entityManager, TB_ARTICLES ta){
return new JPAQuery<TB_ARTICLES>(entityManager)
.select(tb_articles.articleSeq.count())
.from(tb_articles)
.where(
ta.getBbsInfoSeq() == 0 ?
articleWhere(tb_articles, ta) :
tb_articles.bbsInfoSeq.eq(ta.getBbsInfoSeq())
.and(articleWhere(tb_articles, ta))
);
}
그리고 위의 쿼리를 아래와같이 사용한다.
/**
* 게시글 목록 paging
* @param entityManager
* @param ta
* @param pageable
* @return
*/
default Page getArticleList(EntityManager entityManager, TB_ARTICLES ta, Pageable pageable){
return pagination(
getArticleListCNTQuery(entityManager, ta),
getArticleListQuery(entityManager, ta),
pageable
);
}
위 함수의 호출은 아래와같이 한다.
/**
* 게시글 목록
* @param tb_articles
* @return
* @throws Exception
*/
public Page<TB_ARTICLES> getArticleList(TB_ARTICLES tb_articles) throws Exception{
return articleMngRepository.getArticleList(
entityManager,
tb_articles,
PageRequest.of(tb_articles.getPageIndex(), tb_articles.getRecordCountPerPage())
);
}
매개변수로 현재 페이지와 출력 페이지수를 넘겨주는것을 확인 할 수 있다.
실제 구현된 결과는 아래와 같다.
(pageIndex가 2가되면 2 of 4 containing, 3이되면 3 of 4..)
방법2. offset / limit 함수를 통한 paging 구현
방법2는 단순하게 query select시 offset과 limit을 걸고 가지고오는것이다.
방법1에서는 현재 보고있는 페이지와 페이지 출력수를 매개변수로 넘기면 알아서 offset을 계산해주지만,
해당 방법의 경우 offset을 직접 계산해야 한다.
그리고, totalCount에 대한 결과도 따로 가지고있어야 한다. 이는 리스트 출력시 rownum 계산에 사용된다.
offset 계산은 아래와 같이 한다.
public int getFirstIndex() {
firstIndex = ((this.getPageIndex()-1)*this.getRecordCountPerPage());
return firstIndex;
}
firstIndex를 가져올때 현재페이지와 페이지 출력수를 받아 계산 후 출력이 시작되어야 하는 지점을 리턴해준다.
그리고 RownumUtil을 생성해주자.
(전체페이지수, 현재페이지, 노출페이지를 인자로 받아 rownum을 계산해준다.)
public class RownumUtil {
private long totalCount = 0;
private long pageIndex = 0;
private int recordCountPerPage = 0;
private int itemSize = 0;
private long rowNum = 0;
public RownumUtil() {
}
public void pageCalc() {
rowNum = this.totalCount < (this.totalCount - (this.pageIndex * this.recordCountPerPage)) ? this.itemSize : (this.totalCount - ((this.pageIndex - 1) * this.recordCountPerPage));
}
public long getRowNum() {
long result = rowNum;
rowNum--;
return result;
}
public RownumUtil(long totalCount,
long pageIndex,
int recordCountPerPage) {
this.totalCount = totalCount;
this.pageIndex = pageIndex;
this.recordCountPerPage = recordCountPerPage;
pageCalc();
}
}
해당 도메인에 getter, setter로 해당 유틸을 생성해주자.
그리고 게시글의 전체 수를 카운트 한 후 해당 도메인의 setter에 세팅해주자.
(전체페이지수, 현재페이지, 페이지 출력수를 param에 넣고있다.)
int totalCount = articleMngService.getArticleListCNT(tb_articles);
tb_articles.setRownumUtil(new RownumUtil(totalCount,tb_articles.getPageIndex(),tb_articles.getRecordCountPerPage()));
그리고 위 과정이 끝날 경우 아래와 같이 사용한다.
/**
* 게시글 목록
* @param entityManager
* @param ta
* @return
*/
default List<TB_ARTICLES> getArticleList(EntityManager entityManager, TB_ARTICLES ta) {
return new JPAQuery<TB_ARTICLES>(entityManager)
.select(
Projections.bean(
tb_articles,
tb_articles.articleSeq,
tb_articles.bbsInfoSeq,
Projections.bean(
tb_bbs_category,
tb_bbs_category.categoryCd,
tb_bbs_category.categoryNm
).as("tb_bbs_category"),
tb_articles.subject,
tb_articles.delYn,
tb_articles.secretYn,
tb_articles.noticeYn,
tb_articles.anonYn,
tb_articles.userId,
tb_articles.prtSeq,
tb_articles.prtGrp,
tb_articles.bbsOrder,
tb_articles.inptIp,
tb_articles.updIp,
tb_articles.readCnt,
tb_articles.inptUserName,
tb_articles.inptDate,
tb_articles.updUserName,
tb_articles.updDate,
ExpressionUtils.as(
new JPAQuery<TB_ARTICLE_COMMENTS>(entityManager)
.select(tb_article_comments.commentSeq.count())
.from(tb_article_comments)
.where(tb_article_comments.articleSeq.eq(tb_articles.articleSeq).and(tb_article_comments.delYn.eq("N"))),
"comment_cnt"
),
ExpressionUtils.as(
new JPAQuery<TB_FILES>(entityManager)
.select(tb_files.fileSeq.count())
.from(tb_files)
.where(tb_files.tableSeq.eq(tb_articles.articleSeq).and(tb_files.tableNm.eq("TB_ARTICLES"))),
"file_cnt"
),
Projections.bean(
tb_bbs_info,
tb_bbs_info.bbsCd,
tb_bbs_info.bbsNm
).as("tb_bbs_info")
)
).from(tb_articles)
.innerJoin(tb_bbs_info)
.on(tb_bbs_info.bbsInfoSeq.eq(tb_articles.bbsInfoSeq))
.leftJoin(tb_bbs_category)
.on(tb_bbs_category.bbsInfoSeq.eq(tb_articles.bbsInfoSeq).and(tb_bbs_category.categoryCd.eq(tb_articles.categoryCd)))
.where(
ta.getBbsInfoSeq() == 0 ?
articleWhere(tb_articles, ta) :
tb_articles.bbsInfoSeq.eq(ta.getBbsInfoSeq())
.and(articleWhere(tb_articles, ta)
)
)
.orderBy(tb_articles.prtGrp.desc(), tb_articles.bbsOrder.asc().nullsFirst())
.offset(ta.getFirstIndex()) // 페이지 시작부분 (0, 10, 20 -)
.limit(ta.getRecordCountPerPage()) // 페이지 출력 수 (10, 20, 30 -)
.fetch().stream().map(v -> {
v.setrNum(ta.getRownumUtil().getRowNum()); // rownum 부여
return v;
}).collect(Collectors.toList());
}
하단 offset, limit부분만 확인하면 된다.
아래 람다 부분은 rownum 부여를 위한 부분이다.
전체 게시글 수를 조회하고 RownumUtil을 생성했던 부분을 통해 게시글마다 현재의 rownum이 매겨지게 된다.
해당함수는 그냥 그대로 호출하고, List형식으로 받아서 사용하면 된다.
마치며..
사실 1의 방법이 더 나은지, 2의 방법이 더 나은지는 잘 모르겠다.
다만.. 2의 방식은 xbatis를 사용할때 계속 써오던 방식이니, 1의 방식으로 사용하는걸 습관들여야 할 듯 하다.
'개발 이야기 > SPRING' 카테고리의 다른 글
- Total
- Today
- Yesterday
- Java
- 저공해자동차
- WebFlux
- intellij
- 국비교육
- Thymeleaf
- Spring Cache
- Weblogic
- Spring Security
- CSRF
- JPA
- 친환경차
- SpringDataJPA
- 이직
- 취업
- memcached
- spring-data-jpa
- SI
- Spring
- Util
- spring webflux
- 스프링
- spring-jpa
- 저공해자동차 스티커
- Spring Boot
- query-dsl
- multipart
- hibernate
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |