バッチ完了後にPDFを作る構成とContentVersion化
取引先を作成するバッチの完了後に、そのPDFを作成する別のバッチをfinishメソッドから呼び出す構成を説明します。PDFの保存先は、廃れたAttachmentではなくContentVersionに書き直します。
2つのバッチをつなげる理由
Apexバッチは、1回のトランザクションで扱えるヒープやSOQL発行回数に上限があります。レコード作成とPDF生成を1つのバッチに詰め込むと、対象件数が増えた日に上限へ当たります。finishメソッドから次のバッチを呼び出せば、処理を分けたまま順番に実行できます。
バッチ1で取引先を作成する
対象を1件取得し、バッチキーを付けて新しい取引先を作ります。finishから次のバッチへバッチキーを引き継ぎます。
global class AccountCreateBatch implements Database.Batchable<sobject>, Database.Stateful { String batchKey = 'AccountCreateBatch' + DateTime.now().format('yyyyMMddhhmmssSSS'); public Database.QueryLocator start(Database.BatchableContext dbc) { return Database.getQueryLocator('SELECT Id, Name FROM Account ORDER BY Id ASC LIMIT 1'); } public void execute(Database.BatchableContext dbc, List<Account> accList) { List<Account> insertAccountList = new List<Account>(); for (Account acc : accList) { insertAccountList.add(new Account(Name = acc.Name, BatchKey__c = batchKey)); } if (!insertAccountList.isEmpty()) { insert insertAccountList; } } public void finish(Database.BatchableContext dbc) { Database.executeBatch(new AccountPDFCreateBatch(batchKey), 1); } }
取引先を1件作成し、finishから後続のPDF作成バッチを呼び出します。
バッチ2をContentVersionで書き直す
以前の書き方ではPDFの保存先にAttachmentを使っていましたが、SalesforceはAttachment単体ではなく、FilesとNotesを分けて使うことをSpring '17以降案内しています。ここではContentVersionに書き直します。FirstPublishLocationIdに取引先のIDを入れると、ContentDocumentLinkが自動で作られます。SOQLの文字列連結もバインド変数に直しています。
public with sharing class AccountPDFCreateBatch implements Database.Batchable<sobject>, Database.Stateful { String batchKey; public AccountPDFCreateBatch(String batchKey) { this.batchKey = batchKey; } public Database.QueryLocator start(Database.BatchableContext dbc) { return Database.getQueryLocator('SELECT Id, Name, BatchKey__c FROM Account WHERE BatchKey__c = :batchKey ORDER BY Id ASC'); } public void execute(Database.BatchableContext dbc, List<Account> accList) { String suffix = '_' + Date.today().month() + '取引先.pdf'; Savepoint sp = Database.setSavepoint(); try { List<ContentVersion> newFiles = new List<ContentVersion>(); for (Account acc : accList) { PageReference pr = Page.AccountPDF; pr.getParameters().put('id', acc.Id); ContentVersion cv = new ContentVersion(); cv.Title = acc.Name + suffix; cv.PathOnClient = acc.Name + suffix; cv.VersionData = Test.isRunningTest() ? Blob.valueOf('myString') : pr.getContentAsPDF(); cv.FirstPublishLocationId = acc.Id; newFiles.add(cv); } if (!newFiles.isEmpty()) { insert newFiles; } } catch (Exception e) { Database.rollback(sp); } } public void finish(Database.BatchableContext dbc) { } }
AttachmentではなくContentVersionにPDFを保存します。バッチキーの絞り込みもバインド変数に直しています。
PDFを描画するVisualforceページ
renderAs="pdf"を付けるとレスポンスがPDFで返ります。コントローラのPdfCtrlは、URLパラメータのidで取引先を1件取得してaccに入れるだけです。
<apex:page controller="PdfCtrl" renderAs="pdf"> <h1>Hello PDF</h1> <apex:outputText value="{!acc.Name}" /> </apex:page>
取引先名を表示するだけのPDF用ページです。
バッチとPDF生成のガバナ制限
非同期Apex(バッチを含む)のヒープ上限は12MBで、同期Apexの6MBより大きく確保されています。SOQL発行回数も、非同期は200回、同期は100回です。PDFにも上限があり、レンダリング前のレスポンスは15MB未満、生成後のPDFは60MB、画像の総容量は30MBまでです。getContentAsPDF()は、トリガー・テストメソッド(API 34.0以降はコールアウト扱い)・Apexメールサービスの中では使えません。上のコードがTest.isRunningTest()で分岐しているのはこのためです。finishからDatabase.executeBatchを呼ぶと、バッチごとにガバナ制限がリセットされます。第2引数のスコープを省略すると既定は200件、QueryLocatorを使うときに指定できるスコープの上限は2,000件です。PDF生成はヒープを使うため、この例では1件に絞っています。
動作確認
開発者コンソールを開く
実行タブに
AccountCreateBatch batch = new AccountCreateBatch(); Database.executeBatch(batch, 1);を入力して実行します。バッチ1が完了すると、finishから自動でバッチ2が呼ばれます。取引先レコードを確認する
作成された取引先に、バッチキーが入っていることを確認します。
関連ファイルを確認する
作成された取引先の関連リストでFilesを開き、月と「取引先」が付いたPDFファイルがあることを確認します。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
| PDFの保存にAttachmentを使い続ける | Filesの関連リストに出てきません |
| SOQLにバッチキーを文字列連結で埋め込む | エスケープ漏れの余地が残ります |
| getContentAsPDF()をテストでそのまま呼ぶ | API 34.0以降はコールアウト扱いです |
| 元データやレイアウトが大きすぎる | 60MB・30MBの上限に当たり失敗します |
確認した環境
- 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、ContentVersionの使い方とバッチ・PDF生成のガバナ制限を確認しています
- ページ名(Page.AccountPDF)やカスタム項目(BatchKey__c)は、ご自身の組織のものに置き換えてください
まとめ
- バッチ1の完了後にバッチ2を呼ぶ構成は、finishメソッドからDatabase.executeBatchを呼ぶだけで組めます
- PDFの保存先はAttachmentではなくContentVersionです。FirstPublishLocationIdにレコードIDを入れると、ContentDocumentLinkは自動で作られます
- SOQLに変数を埋め込むときは、文字列連結ではなくバインド変数を使います
- 非同期Apexのヒープ上限は12MBです。PDF生成はヒープを使うため、スコープを絞って安全側に倒します
- getContentAsPDF()はトリガー・テストメソッド(コールアウト扱い)・Apexメールサービスの中では使えません
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。