 |
to Top
LimitSearch/PagingSearchについて記述します。
※DBFluteは、S2Pagerを参考にしていますがS2Pager自身を利用していません。
// ======================================================================================================
// ConditionBeanによるLimitSearch/PagingSearch
// ===========================================
// -----------------------------------------------------
// LimitSearch
// -----------
ConditionBeanにおいて、「先頭の何件を取得する」という条件の検索が可能です。
ex) BOOKに対してAuthorが30歳以上/登録日時の降順/最初の50件のみ検索
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
final LdBookCB cb = new LdBookCB();
cb.query().queryAuthor().setAuthorAge_GreaterEqual(30);
cb.query().addOrderBy_RTime_Desc();
cb.fetchFirst(50);
final java.util.List<LdBook> ls = dao.selectList(cb);
- - - - - - - - -/
また、「何件から何件を取得する」という条件の検索が可能です。
ex) BOOKに対しAuthorが30歳以上/登録日時の降順/81件目から100件目のみ検索
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
final LdBookCB cb = new LdBookCB();
cb.query().queryAuthor().setAuthorAge_GreaterEqual(30);
cb.query().addOrderBy_RTime_Desc();
cb.fetchScope(80, 20);// 80件目飛ばして20件を取得(81件目から100件目)
final java.util.List<LdBook> ls = dao.selectList(cb);
- - - - - - - - -/
[補足]
{Oracle、FirstBird、MySQL、PostgreSQL}など、SQL文法的にLimitSearchがサポートされているDB
に関しては、そのSQLを利用して検索します(ROWNUMやoffset/limitなど)。
{SQLServer、DB2}など、SQL文法的に「先頭から何件」という検索しかできないDBに関しては、
例えば80-100を取得する際に、先頭から100件を取得しResultSetで先頭80件を飛ばして取得します。
※Adviceを頂きまして、DB2に関しては、ROW_NUMBER()関数を利用したLimitSearchを
検討します(2006/09/28現在)。
// -----------------------------------------------------
// PagingSearch
// ------------
ConditionBeanにおいて、「PageSizeを何件として指定したPageを検索する」という条件の検索が可能です。
ex) BOOKに対してAuthorが30歳以上/登録日時の降順/PageSize20件でPaging検索
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
final LdBookCB cb = new LdBookCB();
cb.query().queryAuthor().setAuthorAge_GreaterEqual(30);
cb.query().addOrderBy_RTime_Desc();
cb.fetchFirst(20);// PageSizeの設定
cb.fetchPage(1);// 1-20
final java.util.List<LdBook> lsFirstPage = dao.selectList(cb);
cb.fetchPage(2);// 21-40
final java.util.List<LdBook> lsSecoundPage = dao.selectList(cb);
cb.fetchPage(3);// 41-60
final java.util.List<LdBook> lsThirdPage = dao.selectList(cb);
- - - - - - - - -/
// -----------------------------------------------------
// PagingResultBean
// ----------------
DBFluteには、Pagingの結果を管理するObjectが用意されています。
そのObjectを効率よく利用するMethodがBehaviorに存在します。
ex) BOOKに対してAuthorが30歳以上/登録日時の降順/PageSize20件でPaging検索
※該当のConditionBeanにて条件に合致する総Record数が76件だと仮定します。
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
final LdBookCB cb = new LdBookCB();
cb.query().queryAuthor().setAuthorAge_GreaterEqual(30);
cb.query().addOrderBy_RTime_Desc();
cb.fetchFirst(20);// PageSizeの設定
cb.fetchPage(1);// 1-20
final LdPagingResultBean<LdBook> rbFirstPage = bhv.selectPage(cb);
cb.fetchPage(2);// 21-40
final LdPagingResultBean<LdBook> rbSecondPage = bhv.selectPage(cb);
cb.fetchPage(3);// 41-60
final LdPagingResultBean<LdBook> rbThirdPage = bhv.selectPage(cb);
cb.fetchPage(4);// 61-76
final LdPagingResultBean<LdBook> rbFourthPage = bhv.selectPage(cb);
// ***************************************
// 「1」Page目のPaging結果についてのAssert
// ***************************************
// 検索したTable名(結合の中心Table) → BOOK
assertEquals("BOOK", rbFirstPage.getTableDbName());
// 総Record数 → 76
assertEquals(76, rbFirstPage.getAllRecordCount());
// 該当PageのEntityのList → 件数はPageSizeと同じ
assertEquals(20, rbFirstPage.getSelectedList().size());
// OrderByの一番目の要素のASC/DESC → DESC
assertTrue(getOrderByClause().isFirstElementDesc());
// OrderByの一番目の要素の列名 → R_TIME
assertTrue(getOrderByClause().isSameAsFirstElementColumnName("R_TIME"));
// PageSize → 20
assertEquals(20, rbFirstPage.getPageSize());
// 現在のPage番号 → 1
assertEquals(1, rbFirstPage.getCurrentPageNumber());
// 総Page数 → 4 ※allRecordCountとpageSizeから計算
assertEquals(4, rbFirstPage.getAllPageCount());
// 前のPageがあるか否か → 現在は1Page目なのでFalse
assertFalse(rbFirstPage.isExistPrePage());
// 次のPageがあるか否か → 次の2Page目があるのでTrue
assertTrue(rbFirstPage.isExistNextPage());
// ***************************************
// 「2」Page目のPaging結果についてのAssert
// ***************************************
// ※共通部のAssertは省略{それらは1Page目と同じ値となります}
// 現在のPage番号 → 2
assertEquals(2, rbSecondPage.getCurrentPageNumber());
// 前のPageがあるか否か → 現在は2Page目なのでTrue
assertTrue(rbSecondPage.isExistPrePage());
// 次のPageがあるか否か → 次の3Page目があるのでTrue
assertTrue(rbSecondPage.isExistNextPage());
// ***************************************
// 「3」Page目のPaging結果についてのAssert
// ***************************************
// 現在のPage番号 → 3
assertEquals(3, rbThirdPage.getCurrentPageNumber());
// 前のPageがあるか否か → 現在は3Page目なのでTrue
assertTrue(rbThirdPage.isExistPrePage());
// 次のPageがあるか否か → 次の4Page目があるのでTrue
assertTrue(rbThirdPage.isExistNextPage());
// ***************************************
// 「4」Page目のPaging結果についてのAssert
// ***************************************
// 現在のPage番号 → 4
assertEquals(4, rbFourthPage.getCurrentPageNumber());
// 該当PageのEntityのList → 最後のPageなので件数は16件
assertEquals(16, rbFourthPage.getSelectedList().size());
// 前のPageがあるか否か → 現在は4Page目なのでTrue
assertTrue(rbFourthPage.isExistPrePage());
// 次のPageがあるか否か → 次の5Page目はないのでFalse
assertFalse(rbFourthPage.isExistNextPage());
- - - - - - - - -/
もし総Page数が100件以上など膨大な場合に、画面にて全てのPage番号の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 29...]
その場合に「ある限られたPage番号のみ」を表示することがよくあります。
PagingResultBeanは、「ある限られたPage番号」を算出するLogicを 2 Pattern用意しています。
(他にPatternがあるようでしたら相談して下さい)
{PageRange}
[4]Page目を選択 - [1 2 3 4 5 6 7 8 9 次へ]
[13]Page目を選択 - [前へ 8 9 10 11 12 13 14 15 16 17 18 次へ]
[21]Page目を選択 - [前へ 16 17 18 19 20 21 22]
※最大22Pageまでとして
ex) 「ある限られたPage番号」は現在のPage番号の前後1Pageとした場合
※一般的には、前後5Pageとか前後10Pageとかが基本です。
※これらは、設定されたPageRangeSizeとPaging結果を基に都度算出します。(PageRangeSizeの設定を忘れずに)
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// **********************
// 「1」Page目のPageRange
// **********************
// PageRangeSizeは現在のPage番号の前後5Pageとする
rbFirstPage.setPageRangeSize(5);
// 該当するPageRangeのPage番号の配列
final int[] expectedFirstArray = new int[]{1,2,3,4,5,6};
assertEquals(expectedFirstArray, rbFirstPage.pageRange().createPageNumberArray());
// 前のPageRangeがあるか否か → 1Page目より前はないのでFalse
assertFalse(rbFirstPage.pageRange().isExistPrePageRange());
// 次のPageRangeがあるか否か → 7Page目があるのでTrue
assertTrue(rbFirstPage.pageRange().isExistNextPageRange());
// **********************
// 「4」Page目のPageRange
// **********************
// PageRangeSizeは現在のPage番号の前後5Pageとする
rbSecondPage.setPageRangeSize(5);
// 該当するPageRangeのPage番号の配列
final int[] expectedSecondArray = new int[]{1,2,3,4,5,6,7,8,9};
assertEquals(expectedSecondArray, rbSecondPage.pageRange().createPageNumberArray());
// 前のPageRangeがあるか否か → 1Page目より前は無いのでFalse
assertFalse(rbSecondPage.pageRange().isExistPrePageRange());
// 次のPageRangeがあるか否か → 10Page目があるのでTrue
assertTrue(rbSecondPage.pageRange().isExistNextPageRange());
// **********************
// 「13」Page目のPageRange
// **********************
// PageRangeSizeは現在のPage番号の前後5Pageとする
rbThirdPage.setPageRangeSize(5);
// 該当するPageRangeのPage番号の配列
final int[] expectedThirdArray = new int[]{8,9,10,11,12,13,14,15,16,17,18};
assertEquals(expectedThirdArray, rbThirdPage.pageRange().createPageNumberArray());
// 前のPageRangeがあるか否か → 7Page目はあるのでTrue
assertTrue(rbThirdPage.pageRange().isExistPrePageRange());
// 次のPageRangeがあるか否か → 19Page目はあるのでTrue
assertTrue(rbThirdPage.pageRange().isExistNextPageRange());
// **********************
// 「21」Page目のPageRange
// **********************
// PageRangeSizeは現在のPage番号の前後5Pageとする
rbFourthPage.setPageRangeSize(5);
// 該当するPageRangeのPage番号の配列
final int[] expectedThirdArray = new int[]{16,17,18,19,20,21,22};
assertEquals(expectedThirdArray, rbFourthPage.pageRange().createPageNumberArray());
// 前のPageRangeがあるか否か → 15Page目はあるのでTrue
assertTrue(rbFourthPage.pageRange().isExistPrePageRange());
// 次のPageRangeがあるか否か → 23Page目以降はないのでFalse
assertFalse(rbFourthPage.pageRange().isExistNextPageRange());
- - - - - - - - -/
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
[4]Page目を選択 - [1 2 3 4 5 6 7 8 9 次へ]
[13]Page目を選択 - [前へ 8 9 10 11 12 13 14 15 16 17 18 次へ]
[21]Page目を選択 - [前へ 16 17 18 19 20 21 22]
※最大22Pageまでとして
ではなく
[4]Page目を選択 - [1 2 3 4 5 6 7 8 9 10 11 次へ]
[13]Page目を選択 - [前へ 8 9 10 11 12 13 14 15 16 17 18 次へ]
[21]Page目を選択 - [前へ 12 13 14 15 16 17 18 19 20 21 22]
※最大22Pageまでとして
とする場合は以下のように指定することで実現可能です。
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// **********************
// 「1」Page目のPageRange
// **********************
// PageRangeSizeは現在のPage番号の前後1Pageとする
final PageRangeOption option = new PageRangeOption();
option.setPageRangeSize(1);
option.setFillLimit(true);// ☆Point!
rbFirstPage.setPageRangeSize(option);
- - - - - - - - -/
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
{PageGroup}
[4]Page目を選択 - [1 2 3 4 5 6 7 8 9 10 11 次へ]
[13]Page目を選択 - [前へ 11 12 13 14 15 16 17 18 19 20 次へ]
[21]Page目を選択 - [前へ 21 22]
※最大22Pageまでとして
ex) 「ある限られたPage番号」は2Page毎のGroupとする場合
※一般的には、5Page/10Pageとかが基本です。
※これらは、設定されたPageGroupSizeとPaging結果を基に都度算出します。(PageGroupSizeの設定を忘れずに)
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// **********************
// 「1」Page目のPageGroup
// **********************
// PageGroupSizeは10Page毎のGroupとする
rbFirstPage.setPageGroupSize(10);
// 該当するPageGroupのPage番号の配列
final int[] expectedFirstArray = new int[]{1,2,3,4,5,6,7,8,9,10};
assertEquals(expectedFirstArray, rbFirstPage.pageGroup().createPageNumberList()));
// 前のPageGroupがあるか否か → 1Page目より前はないのでFalse
assertFalse(rbFirstPage.pageGroup().isExistPrePageGroup());
// 次のPageGroupがあるか否か → 11Page目があるのでTrue
assertTrue(rbFirstPage.pageGroup().isExistNextPageGroup());
// **********************
// 「4」Page目のPageGroup
// **********************
// PageGroupSizeは10Page毎のGroupとする
rbSecondPage.setPageGroupSize(10);
// 該当するPageGroupのPage番号の配列
final int[] expectedSecondArray = new int[]{1,2,3,4,5,6,7,8,9,10};
assertEquals(expectedSecondArray, rbSecondPage.pageGroup().createPageNumberList()));
// 前のPageGroupがあるか否か → 1Page目より前はないのでFalse
assertFalse(rbSecondPage.pageGroup().isExistPrePageGroup());
// 次のPageGroupがあるか否か → 11Page目があるのでTrue
assertTrue(rbSecondPage.pageGroup().isExistNextPageGroup());
// **********************
// 「13」Page目のPageGroup
// **********************
// PageGroupSizeは10Page毎のGroupとする
rbThirdPage.setPageGroupSize(10);
// 該当するPageGroupのPage番号の配列
final int[] expectedThirdArray = new int[]{11,12,13,14,15,16,17,18,19,20};
assertEquals(expectedThirdArray, rbThirdPage.pageGroup().createPageNumberList()));
// 前のPageGroupがあるか否か → 10Page目より前があるのでTrue
assertTrue(rbThirdPage.pageGroup().isExistPrePageGroup());
// 次のPageGroupがあるか否か → 21Page目があるのでTrue
assertTrue(rbThirdPage.pageGroup().isExistNextPageGroup());
// **********************
// 「21」Page目のPageGroup
// **********************
// PageGroupSizeは10Page毎のGroupとする
rbFourthPage.setPageGroupSize(10);
// 該当するPageGroupのPage番号の配列
final int[] expectedFourthArray = new int[]{21,22};
assertEquals(expectedFourthArray, rbFourthPage.pageGroup().createPageNumberList()));
// 前のPageGroupがあるか否か → 20Page目より前があるのでTrue
assertTrue(rbFourthPage.pageGroup().isExistPrePageGroup());
// 次のPageGroupがあるか否か → 23Page目以降はないのでFalse
assertFalse(rbFourthPage.pageGroup().isExistNextPageGroup());
- - - - - - - - -/
// -----------------------------------------------------
// Behavior.selectPage()の補足
// ---------------------------
Behavior.selectPage()について
このMethodは、以下の処理を一手に引き受けてくれます。
A. 総Record数取得のためのselectCount()の呼び出し
B. 想定外Page数指定の際の再検索(すれ違いなど)
C. Paging検索/PagingResultBeanの生成
{A}
総Record数を取得するために「LimitSearchの条件のみ除去したselectCount()」を実行します。
※総Page数を求めるために必ず必要です。
{B}
例えば、総Record数が61件で画面にて3Page目を開いてたとして、
4Page目に遷移する直前に他のThreadに4Page目に表示するはずの1件が削除されたとします。
(もしくは、現在の条件に合致しない値に更新されたとか)
すると4Page目で検索しても取得できるRecordは0件です。
その場合、selectPage()では、3Page目を検索し直します(その瞬間の最大Page)。
これは画面の非機能要件にあたる部分であり、各Projectによってやりたいことが変わるかもしれませんが、
Defaultでこのような動きをします。(大抵の場合はこの動きで良いのではないかと考えました)
特に、一覧の画面内にてRecordの編集・削除ができる場合に、その処理の後の再検索においてこの動きが利用できます。
4Page目を開いていて61件目を自分で編集・削除したことにより4Page目がなくなってしまう場合は、4Page目を再検索しても
3Page目が結果として帰ってきますので、Programが再検索のときにそこを意識する必要はありません。
(常に自Page番号で検索すれば良い)
もし、この動きではどうしても要件を満たせない場合は、selectPage()の別引数のMethodをご利用ください。
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
selectPage(XxxCB cb, SelectPageInvoker<Xxx> invoker)
- - - - - -/
SelectPageInvokerはInterfaceです。selectPage()はinvokeSelectPage()というMethodをCallbackします。
このMethodを自由に実装することにより、各Projectに合わせた動きを実現することが可能です。
(「A」に関しても同様のことが言えます)
{C}
Paging検索の実行と既に説明した通りのPaggingResultBeanを生成します。
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
複雑な検索の場合は“外だしSQL”を利用します。そのやり方は後述します。
ただ、もしProjectにてViewを利用することができるのであれば、その複雑なSQLの基本部分をViewで実装すれば、
DBFluteはViewを一つのTableとして自動生成しますので、そのViewのConditionBeanでPagingが可能となります。
それが一番楽と言えば楽ですが、管理やMaintenance性を考えた場合にViewを積極利用しないProjectもあるかと思います。
その場合はやはり“外だしSQL”となります。
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
// ======================================================================================================
// “外だしSQL”によるLimitSearch/PagingSearch
// ===========================================
★★★★★★★★★★★★★★★★★★★★★★★★★★★★
【お知らせ】
以下内容は将来削除される可能性があります。
リニューアルされたドキュメントがありますのでこちらをご覧下さい。
★★★★★★★★★★★★★★★★★★★★★★★★★★★★
Pagingで検索する画面は、複雑なSQLになりがちです。なので、外だしSQLでもPagingができなければなりません。
ex) 呼び出し側
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SimplePagingBean pb = new SimplePagingBean();
pb.fetchFirst(20);
pb.fetchPage(4); // 81-100件目
PagingResultBean<LdBook> rb = bhv.selectPageXxx(pb, xxxDate);
- - - - - -/
呼び出し側では、SimplePagingBeanを利用してPaging情報をBehaviorに渡すようにします。
BehaviorのMethodはExに定義するProject独自のMethodとなります。
ex) ExBehaviorの独自Method {例として'xxxDate'を絞込み条件のParameterとする}
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public PagingResultBean<LdBook> selectPageXxx(final SimplePagingBean pb, final Timestamp xxxDate) {
assertObjectNotNull("pb", pb);
assertObjectNotNull("xxxDate", xxxDate);
final SelectPageCallback<LdBook> callback = new SelectPageCallback<LdBook>() {
public PagingBean getPagingBean() {
return pb;
}
public int selectCountIgnoreFetchScope() {
return getMyDao().selectCountXxx(xxxDate);
}
public List<LdBook> selectListWithFetchScope() {
return getMyDao().selectPageXxx(pb, xxxDate);
}
};
return new SelectPageSimpleInvoker<LdBook>(this).invokeSelectPage(callback);
}
- - - - - -/
ExBehaviorに独自に作成する“外だしSQL”を利用したselectPage()を定義します。
SelectPageCallbackのそれぞれの実装MethodにDaoのMethodを指定します。
selectCountIgnoreFetchScope()は、FetchScope(Paging)をしない場合に
該当の条件でHITする件数を取得するMethodを呼び出すようにします。
selectListWithFetchScope()は、実際にPagingの条件を考慮して検索するMethodを呼び出すようにします。
ex) ExDaoの独自Method {例として'xxxDate'を絞込み条件のParameterとする}
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public int selectCountXxx(Timestamp xxxDate);
public java.util.List<LdBook> selectPageXxx(SimplePagingBean pb, Timestamp xxxDate);
- - - - - -/
ExDaoに独自のMethodを定義します。
selectCountXxx()にはPagingBeanの引数は不要です。(Pagingを考慮しない件数を取りたいため)
SimplePagingBeanは、必ず第1引数に指定して下さい。
ex) FetchScope(Paging)を考慮しないSelectCountのSQL
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
select count(*)
from BOOK
where ...(色々複雑な条件...)
- - - - - -/
FetchScope(Paging)をしない場合に該当の条件でHITする件数を取得するSQLを用意します。
ex) PagingのSQL{Oracleの場合}
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
select *
from (select book.*, rownum as rn
from (select BOOK_ID, BOOK_NAME, ...
from BOOK
where ...(色々複雑な条件...)
) book
)
where rn > /*$pb.pageStartIndex*/80
and rn <= /*$pb.pageEndIndex*/100
- - - - - -/
ex) PagingのSQL{MySQLの場合}
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
select BOOK_ID, BOOK_NAME, ...
from BOOK
where ...(色々複雑な条件...)
limit /*$pb.pageStartIndex*/80, /*$pb.fetchSize*/20
- - - - - -/
ex) PagingのSQL{SQLServerの場合}
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
select top /*$pb.pageEndIndex*/100 BOOK_ID, BOOK_NAME, ...
from BOOK
where ...(色々複雑な条件...)
- - - - - -/
Paging検索のSQLを用意します。
DBによってPagingの方法が異なります。
TODO: 書き途中...様々なDBの書き方を追加すること。&動的OrderByの利用方法
// --------------------------------------------------------
// ParameterBeanとSimplePagingBeanの連携
// -------------------------------------
*** @Hint ***
ParameterBeanをSimplePagingBeanの継承Classとして利用することが可能です。
ex) --!BookCollectionStatisticPmb extends SPB!
※詳しくは、Tips: Sql2Entity をご覧下さい。
ParameterBeanとSimplePagingBeanの連携を利用すると、ExBehaviorでの実装は以下のようになります。
ex) ExBehaviorの独自Method
/- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public PagingResultBean<LdBook> selectPageXxx(final LdBookCollectionStatisticPmb pmb) {
assertObjectNotNull("pb", pb);
final SelectPageCallback<LdBook> callback = new SelectPageCallback<LdBook>() {
public PagingBean getPagingBean() {
// ☆ParameterBean自体がPagingBeanのInterfaceをImplementしているためこのままpmbを指定すればよい
return pmb;
}
public int selectCountIgnoreFetchScope() {
return getMyDao().selectCountXxx(pmb);
}
public List<LdBook> selectListWithFetchScope() {
return getMyDao().selectPageXxx(pmb);
}
};
return new SelectPageSimpleInvoker<LdBook>(this).invokeSelectPage(callback);
}
- - - - - -/
|