実際に出会ったエラー「MIXED_DML_OPERATION」の概要と回避策を紹介!

今回は「MIXED_DML_OPERATION」エラーについて、その概要と回避策をご紹介します。

はじめに

こんにちは!
Salesforceではさまざまなエラーがありますが、みなさんは「MIXED_DML_OPERATION」に遭遇したことがありますでしょうか?
今回は、筆者が実際に遭遇した「MIXED_DML_OPERATION」の概要とその回避策について、例を用いて紹介します!

MIXED_DML_OPERATIONとは

MIXED_DML_OPERATIONとは、同一トランザクション内で設定オブジェクトと非設定オブジェクトのDML操作をしようとすると発生するエラーです。

設定オブジェクトとは,メタデータの操作に利用されるオブジェクトで、以下のようなオブジェクトを指します。
・プロファイル
・ユーザー
・グループ
・権限セット ...etc

非設定オブジェクトとは、皆さんがよく知っているオブジェクトのことで、以下のようなオブジェクトを指します。
・取引先
・取引先責任者
・カスタムオブジェクト ...etc



実際に遭遇したエラーの一例

ここからは、実際に遭遇した一例から、どのような操作で「MIXED_DML_OPERATION」が発生するのかご紹介します。

以下のシナリオは同一トランザクション内で、ユーザの作成し、権限セットを付与します。そして作成したユーザを所有者とした取引先レコードを作成します。
public with sharing class CheckMixedDmlOperation {
    public CheckMixedDmlOperation() {

    }

    public void checkMixedDmlOpe(){

        //ユーザを作成
        User u = createUser();
        insert u;
        System.debug('ユーザを登録しました');

        //権限セットを取得し、ユーザに割り当てる
        PermissionSet ps = [SELECT Id,Name FROM PermissionSet WHERE Name = 'TestPermSet' LIMIT 1];

        PermissionSetAssignment psa = new PermissionSetAssignment();
        psa.AssigneeId = u.Id;
        psa.PermissionSetId = ps.Id;
        insert psa; //設定オブジェクトのDML操作
        System.debug('権限セットを割り当てました');

        //取引先を作成
        Account acc = createAccount(u.Id);
        insert acc; //非設定オブジェクトのDML操作
        System.debug('取引先を作成しました');

    }
    //ユーザを作成
    public user createUser(){

        profile Pf = [SELECT Id,name FROM profile WHERE name = '検証用プロファイル' LIMIT 1];

            User u = new User();
            u.ProfileId = pf.Id;
            u.LastName = 'testuser';
            u.Alias = 'testUser';
            u.Email = 'test@xx.xxx.com';
            u.UserName = 'test.y@test.com';
            u.EmailEncodingKey = 'ISO-2022-JP';
            u.LanguageLocaleKey = 'ja';
            u.LocaleSidKey = 'ja_JP';
            u.TimeZoneSidKey = 'Asia/Tokyo';
            return u;
    }

    //取引先を作成
    public Account createAccount(Id uId){
        Account acc = new Account();
        acc.Name = 'test';
        acc.ownerId = uId;
        return acc;
    }
}
CheckMixedDmlOperation
匿名コードでメソッドを実行すると、「MIXED_DML_OPERATION」が発生します。
権限セットの割り当てと取引先作成を同時に行っているためです。

MIXED_DML_OPERATION

回避策

では、どのようにしてエラーを回避すればいいのでしょうか。
ここでは2つの「MIXED_DML_OPERATION」の回避策を紹介します。

非同期処理にする

非同期処理でDML操作を行うことで、「MIXED_DML_OPERATION」を回避することができます。

非同期処理では@futureメソッドがスタンダードですが、今回はQueueable インターフェースを使用します。
その理由はQueueable インターフェースはQueueableApexからQueueableApexを呼び出することができるからです。
今回は、設定オブジェクトを作成後に非設定オブジェクトを作成したいので、Queueable インターフェースを使用していきます。

CheckMixedDmlOperation_Queueableクラスで、ユーザ作成・権限セットの割り当てを行い、CreateAccount_Queueable クラスにユーザのIDを渡します。そしてCreateAccount_Queueable クラスを呼び出して、作成したユーザを所有者とした取引先の作成を実行します。
public class CheckMixedDmlOperation_Queueable implements Queueable {

    public CheckMixedDmlOperation_Queueable() {
    }

    public void execute(QueueableContext context) {
        System.debug('CheckMixedDmlOperation_Queueable:execute メソッドを開始しました。');

        // プロファイルと権限セットを取得
        Profile Profile = [SELECT Id, Name FROM Profile WHERE Name = '検証用プロファイル' LIMIT 1];
        PermissionSet ps = [SELECT Id, Name FROM PermissionSet WHERE Name = 'testPermSet' LIMIT 1];

        // ユーザの作成
        User u = createUser(Profile.Id);
        insert u;
        System.debug('ユーザが作成されました');

        //権限セットの割り当て
        PermissionSetAssignment psa = new PermissionSetAssignment();
        psa.AssigneeId =u.Id;
        psa.PermissionSetId = ps.Id;
        insert psa; //設定オブジェクトのDML操作
        System.debug('権限セットを割り当てました');

        //取引先作成のQueueableジョブをキューに入れる
        System.enqueueJob(new CreateAccount_Queueable(u.Id));
        System.debug('CreateAccount_Queueable ジョブをキューに入れました。');
    }

    //ユーザ作成
    private static User createUser(Id profileId) {
        User u = new User();
        u.ProfileId = profileId;
        u.LastName = '検証用ユーザ';
        u.Alias = 'testUser';
        u.Email = 'test@xx.xxx.com';
        u.UserName = 'test.xxx@test.com';
        u.EmailEncodingKey = 'ISO-2022-JP';
        u.LanguageLocaleKey = 'ja';
        u.LocaleSidKey = 'ja_JP';
        u.TimeZoneSidKey = 'Asia/Tokyo';
        return u;
    }
}
CheckMixedDmlOperation_Queueable
public class CreateAccount_Queueable implements Queueable {

    private Id userId;

    public CreateAccount_Queueable(Id userId) {
        this.userId = userId;
    }

    public void execute(QueueableContext context) {
        System.debug('CreateAccount_Queueable:execute メソッドを開始しました。');
        //取引先の作成
        Account acc = new Account();
        acc.Name = '株式会社ABC';
        acc.OwnerId = this.userId;
        insert acc; //非設定オブジェクトのDML操作
        
        System.debug('取引先が作成されました');

    }
}
CreateAccount_Queueable
上記のAPEXクラスを実行すると、権限セットが割り当てられた「検証用ユーザ」と検証用ユーザが所有者の取引先「株式会社ABC」が作成されていることが分かります。

検証用ユーザ

株式会社ABC

@TestSetupを使用する※テストクラスの場合

テストクラスの場合は、@TestSetupを使用することで「MIXED_DML_OPERATION」を回避することができます。
テストクラスではSystem.RunAsで使用するためのテストユーザを作成することが多いと思います。ユーザに権限を付与したり、ユーザをレコード所有者にするといったパターンでのエラー発生が多くあると予想できます。
その場合は、ユーザ作成と権限付与を@TestSetup内で行うようにしてみてください。

まとめ

遭遇すると少し大変な「MIXED_DML_OPERATION」を紹介しました。
今後、プロファイルから権限セットが主流になる中で、権限セットの割り当てとオブジェクトの更新を行うことが増え、このエラーの遭遇率が高まるのではないでしょうか。

今回は権限セットの割り当てを例にとりましたが、非設定オブジェクトのDML操作はほかにもありますので、このエラーに出くわさないためにも、設定オブジェクトと非設定オブジェクトを意識して、開発してみてください!