Apex

バッチ完了後にPDFを作る構成とContentVersion化

取引先を作成するバッチの完了後に、そのPDFを作成する別のバッチをfinishメソッドから呼び出す構成を説明します。PDFの保存先は、廃れたAttachmentではなくContentVersionに書き直します。

2023.03.15

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件に絞っています。

動作確認

  1. 開発者コンソールを開く

    実行タブにAccountCreateBatch batch = new AccountCreateBatch(); Database.executeBatch(batch, 1);を入力して実行します。バッチ1が完了すると、finishから自動でバッチ2が呼ばれます。

  2. 取引先レコードを確認する

    作成された取引先に、バッチキーが入っていることを確認します。

  3. 関連ファイルを確認する

    作成された取引先の関連リストで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との連携まで承ります。状況を伺ったうえで、進め方をご提案します。