0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Apex実装のベストプラクティス(前編)

Last updated at Posted at 2025-02-25

Apexは ガバナ制限(Governor Limits) が厳しく、効率の悪いコードを書くと SOQLクエリの上限(1回のトランザクションで最大100回) に達してしまい、エラーが発生します。
ここでは、ベストプラクティスとアンチパターン を初心者にもわかるように、具体的なコード例を使って整理しました。

後編:


❌ 1. アンチパターン: ループ内のクエリ

問題点

  • ループ内でSOQLクエリを実行すると、短時間でクエリ制限(100回)に達してしまう。
  • 大量データ処理に向いていない(ガバナ制限違反)。

❌ 悪い例(NGコード)

public void updateAccountContacts() {
    List<Account> accounts = [SELECT Id, Name FROM Account]; // 1回目のクエリ

    for (Account acc : accounts) {
        List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // ❌ ループ内クエリ
        for (Contact con : contacts) {
            con.LastName = acc.Name + ' Customer';
        }
        update contacts; // DML操作もループ内にあるとさらに悪化
    }
}

💥 何が問題か?

  • 取引先(Account)の数だけSOQLが実行される。(100件以上ならエラー)
  • 非効率なDML(データ更新) でガバナ制限に引っかかる。

✅ 良い例(修正コード)

public void updateAccountContacts() {
    // 1回のクエリでアカウント情報を取得
    List<Account> accounts = [SELECT Id, Name FROM Account];

    // 取得したアカウントのIDをリスト化
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : accounts) {
        accountIds.add(acc.Id);
    }

    // 1回のクエリで対象のコンタクトを取得(親のIDでフィルタ)
    List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];

    // まとめてデータを更新
    for (Contact con : contacts) {
        con.LastName = 'Updated Customer';
    }
    update contacts; // 1回のDMLで処理
}

✨ 修正のポイント

  • 1回のSOQLで必要なデータをまとめて取得
  • ループ内ではなく、1回のDMLで一括更新
  • クエリ回数とDML回数を最小化し、ガバナ制限を回避

✅ 2. 原則: コレクションの使用によるクエリ数の最小化

コレクション(SetやMap)を活用することで、クエリの回数を削減できる。

✅ 良い例(Setを活用)

public void updateContactsUsingCollections() {
    List<Account> accounts = [SELECT Id, Name FROM Account];
    
    // Setを使ってアカウントIDを一括管理
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : accounts) {
        accountIds.add(acc.Id);
    }

    // アカウントIDを一括検索してコンタクトを取得
    List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];

    // Mapを使ってアカウント名を取得
    Map<Id, String> accountNameMap = new Map<Id, String>();
    for (Account acc : accounts) {
        accountNameMap.put(acc.Id, acc.Name);
    }

    // 一括更新
    for (Contact con : contacts) {
        con.LastName = accountNameMap.get(con.AccountId) + ' Customer';
    }
    update contacts;
}

✨ 修正のポイント

  • Setを使い、対象のIDリストを作成(重複なし)
  • Mapを使い、関連情報(Account名)を取得
  • 最小限のSOQLで最大限のデータを取得し、効率化

⚠️ 3. 注意: 親-子クエリのカウントは異なる

親オブジェクトのレコード数 vs 子オブジェクトのレコード数

List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account];

この場合、クエリの回数は1回だが、実際に取得するレコード数は「親の件数×子の件数」になる。


✅ 4. 原則: 必要なものを単一のクエリで取得

1回のSOQLで、できるだけ多くの情報を取得することで、クエリの回数を減らす。

❌ 悪い例(複数回のクエリ)

public void processAccounts() {
    List<Account> accounts = [SELECT Id FROM Account];

    for (Account acc : accounts) {
        List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // ❌ ループ内クエリ
        System.debug('Contacts: ' + contacts.size());
    }
}

✅ 良い例(1回のクエリで済ませる)

public void processAccounts() {
    List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account];

    for (Account acc : accounts) {
        System.debug('Contacts: ' + acc.Contacts.size()); // ✅ ループ内でクエリなし
    }
}

🎯 まとめ

項目 ❌ 悪い例 ✅ 良い例
ループ内のクエリ ループの中でSOQL実行 1回のクエリでデータ取得
コレクションの活用 都度クエリで取得 Set/Mapでデータ管理
親-子のレコード数 親と子を個別に取得 関連オブジェクトをネスト取得
単一クエリで取得 必要なデータを何度も取得 1回のクエリで最大限の情報を取得
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?