今回はパフォーマンスの分野からもう一つ、ページングの実装についてです。
表示速度にもビューステートにも優しい
ページングを使うことによって、ビューステートのサイズや表示時間が分割され、パフォーマンスの向上に繋がります。
ページングにはStndardSetControllerを使います。
これは、ページングを使うために作られたようなクラスで、ページングのために必要なアクションが一通り揃っています。
ページング実装例
今回は取引先全件を単純に取得して取引先名を表示します。
簡単なVisualForceとApexクラスで実装すると以下のような形です。
AccountPaging.page
<apex:page controller="PagingCtrl" action="{!init}" readOnly="true">
<apex:form >
<apex:pageBlock >
<!-- ページネーション -->
<apex:pageBlockButtons>
<apex:outputPanel layout="none" rendered="{!enablePrev}">
<apex:commandLink value="< 前" action="{!prev}" />
<apex:outputLabel value=" | " />
</apex:outputPanel>
<apex:outputLabel value="{!currentPage}/{!totalPage}" />
<apex:outputPanel layout="none" rendered="{!enableNext}">
<apex:outputLabel value=" | " />
<apex:commandLink value="次 >" action="{!next}" />
</apex:outputPanel>
</apex:pageBlockButtons>
<!-- ページサイズ分のリストを表示 -->
<apex:pageBlockTable value="{!AccountList}" var="acc">
<apex:column value="{!acc.Name}"/>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>
PagingCtrl.cls
public with sharing class PagingCtrl { // 1ページあたりの表示件数 private static final Integer PAGE_SIZE = 10; // 現在選択中のページ public Integer currentPage {get; private set;} // ページ数 public Integer totalPage {get; private set;} // ページング用StandardSetController private ApexPages.StandardSetController ssController; // 前へボタンが有効か public Boolean getEnablePrev(){ return ssController.getHasPrevious(); } // 次へボタンが有効か public Boolean getEnableNext(){ return ssController.getHasNext(); } // コンストラクタ public PagingCtrl(){ } public PageReference init() { // ページング対象のリストをStandardSetControllerに入れる ssController = new ApexPages.StandardSetController([SELECT Id, Name FROM Account]); currentPage = ssController.getPageNumber(); ssController.setPageSize(PAGE_SIZE); // ページの総数を計算 // レコード数 / ページサイズで計算、小数部分は切り上げ totalPage = (Integer)Math.ceil((Decimal)ssController.getResultSize() / PAGE_SIZE); return null; } // 次へボタンクリックアクション public void next() { ssController.next(); currentPage = ssController.getPageNumber(); } // 前へボタンクリックアクション public void prev() { ssController.previous(); currentPage = ssController.getPageNumber(); } // 現ページのレコードを取得 public List<Account> getAccountList(){ return (List<Account>)ssController.getRecords(); } }
まず、ポイントとしては、ページングを使うということは、それだけレコードの件数が多いということになりますので、VisualForceはReadonlyを使用しましょう。
これにより、扱えるレコードの件数が50,000件から1,000,000件とガバナ制限が緩和されます。
こちらでReadonlyアノテーションを付ける方法を紹介しましたが、VisualForce側につければApex側は特に指定する必要はありません。
レコードをセットする際は、対象のレコード全てをセットし、その際ページサイズも一緒に設定します。
ページ単位でリストを表示する場合は、getRecordsメソッドでページ内に表示するレコードを取得します。
あとは、ページ送りや現在ページを取得するメソッドはあるので、次へや前へのアクションでそれを使います。
コマンドリンクの表示はVisualForce側で調整するため、判定用のメソッドをApexにも用意しています。
残念ながら、ページ数合計を取得するメソッドはなかったので、それだけ自力で計算しています。
数学的知識になりますが、ガウス関数のような計算式を使用すれば合計ページ数を出すことができます。
ビューステート比較
まずは、ページサイズを10にしたときと1000にしたときでビューステートを比較しました。
対象レコードはいずれも1009件としています。
こちらがページサイズを10にした時、
こちらがページサイズを1000にした時、
この結果から、StndardSetControllerのSizeはページサイズには依存しないことが分かります。
でも、Internalはページサイズが大きいほど、Sizeも大きくなることが分かります。
こちらが実際に表示されているリストの内容を保持しているんでしょう。
今回は1000件程度で試しましたが、StndardSetControllerを使うとビューステートも結構抑えられていい感じです。
ただ、これは表示に限った話なので、インライン編集などで保存を伴う時は独自でページング処理を作る必要があります。
実行時間比較
今度はページサイズ10と1000で実行時間の比較をしました。
開発コンソールでタイムラインを表示する機能があるのでそれを使って比較しました。
こちらがページサイズを10にした時、
こちらがページサイズを1000にした時。
この通り、実行時間となるとページサイズが増えると大きくなります。
単純に画面内のデータ量に比例するわけです。
まとめ
- ページングにはStndardSetControllerを使用する
- StndardSetControllerにはレコード全件分セットする
- StndardSetControllerはビューステートが抑えられ、ページサイズには依存しない
- ページサイズに比例して処理時間が長くなる
コメント