Skip to main content
Build the future with Agentforce at TDX in San Francisco or on Salesforce+ on March 5–6. Register now.

Apex 트리거 시작하기

학습 목표

이 유닛을 완료하면 다음을 수행할 수 있습니다.

  • Salesforce 개체에 대한 트리거를 작성할 수 있습니다.
  • 트리거 컨텍스트 변수를 사용할 수 있습니다.
  • 트리거에서 클래스 메서드를 호출할 수 있습니다.
  • 트리거의 sObject addError() 메서드를 사용하여 저장 작업을 제한할 수 있습니다.
참고

참고

한국어로 학습하시겠어요? Trailhead playground에서 한국어로 실습 과제를 시작하고, 괄호 안에 제공된 번역을 사용해 탐색해 보세요. 영어 데이터를 기반으로 실습 과제 검증이 이루어지므로 영문으로 표시된 값만 복사해 붙여 넣습니다. 한국어 조직에서 실습 과제를 통과하지 못한 경우, (1) 로캘을 미국을 바꾸고 (2) 여기에 제시된 지침에 따라 언어를 영어로 바꾼 후 (3) "Check Challenge(과제 확인)" 버튼을 눌러 다시 진행해 보세요.

원하는 언어로 Trailhead 사용하기 뱃지를 확인해 현지화된 Trailhead 경험을 활용하는 방법에 대해 자세히 알아보세요.

시작하기 전에

Apex 트리거는 유용하고 재미있으며 훌륭합니다. 이 모듈을 통해 이러한 기능을 시작할 수도 있으며 다른 Salesforce 기능도 알 수 있기 때문에 Apex 트리거의 성능을 확인할 수도 있습니다. 이 모듈을 최대한 활용하려면 먼저 다음 모듈을 확인하는 것이 좋습니다.

Apex 트리거 작성하기

Apex 트리거를 사용하면 삽입, 업데이트 또는 삭제와 같은 Salesforce의 레코드에 대한 이벤트 전후에 맞춤형 작업을 수행할 수 있습니다. 데이터베이스 시스템이 트리거를 지원하는 것과 같이 Apex는 레코드 관리를 위한 트리거 지원을 제공합니다.

일반적으로 트리거를 사용하여 특정 조건에 따라 작업을 수행하거나 관련 레코드를 수정하거나 특정 작업이 발생하지 않도록 제한합니다. 트리거를 사용하여 SOQL 및 DML 실행 또는 맞춤형 Apex 메서드 호출을 포함하여 Apex에서 할 수 있는 모든 작업을 수행할 수 있습니다.

트리거를 사용하여 Salesforce 사용자 인터페이스에서 포인트 앤 클릭 도구로 수행 불가능한 작업을 수행할 수 있습니다. 예를 들어 필드 값의 유효성을 검사하거나 레코드의 필드를 업데이트하는 경우 유효성 검사 규칙과 플로를 사용합니다. 성능과 확장이 중요하거나, 포인트 앤 클릭 도구용으로는 논리가 너무 복잡하거나, CPU에 부담을 주는 작업을 실행할 때는 Apex 트리거를 사용합니다.

계정 또는 연락처와 같은 최상위 표준 개체, 맞춤형 객체 및 일부 표준 하위 개체에 대해 트리거를 정의할 수 있습니다. 트리거는 생성 시 기본적으로 활성화됩니다. Salesforce는 지정된 데이터베이스 이벤트가 발생할 때 자동으로 활성 트리거를 실행합니다.

트리거 구문

트리거 정의의 구문은 클래스 정의의 구문과 다릅니다. 트리거 정의는 trigger 키워드로 시작합니다. 그런 다음 트리거 이름, 트리거가 연결된 Salesforce 개체 및 트리거가 실행되는 조건이 따릅니다. 트리거의 구문은 다음과 같습니다.

trigger TriggerName on ObjectName (trigger_events) {
   code_block
}

삽입, 업데이트, 삭제 및 삭제 취소 작업 전후에 트리거를 실행하려면 쉼표로 구분된 목록에 여러 트리거 이벤트를 지정합니다. 지정할 수 있는 이벤트는 다음과 같습니다.

  • before insert
  • before update
  • before delete
  • after insert
  • after update
  • after delete
  • after undelete

트리거 예제

이 간단한 트리거는 계정을 삽입하고 디버그 로그에 메시지를 쓰기 전에 실행됩니다.

  1. Developer Console에서 File(파일) | New(새로 만들기) | Apex Trigger(Apex 트리거)를 클릭합니다.
  2. 트리거 이름에 HelloWorldTrigger를 입력한 다음 sObject에 대한 Account(계정)를 선택합니다. Submit(제출)을 클릭합니다.
  3. 기본 코드를 다음으로 바꿉니다.


    trigger HelloWorldTrigger on Account (before insert) {
    	System.debug('Hello World!');
    }
  4. 저장하려면 Ctrl+S를 누릅니다.
  5. 트리거를 테스트하려면 계정을 만듭니다.
    1. Debug(디버그) | Open Execute Anonymous Window(익명 실행 창 열기)를 클릭합니다.
    2. 새 창에서 다음을 추가한 다음 Execute(실행)를 클릭합니다.


      Account a = new Account(Name='Test Trigger');
      insert a;
  1. 디버그 로그에서 Hello World! 구문을 찾습니다. 또한 로그는 트리거가 실행되었음을 보여줍니다.

트리거 유형

트리거에는 두 가지 유형이 있습니다.

  • Before triggers - 데이터베이스에 저장하기 전에 레코드 값을 업데이트하거나 유효성을 검사하는 데 사용됩니다.
  • After triggers - 시스템에서 설정한 필드 값(예: 레코드의 Id 또는 LastModifiedDate 필드)에 액세스 및 다른 레코드의 변경 사항에 영향을 미치는 데 사용됩니다. after trigger가 발생한 레코드는 읽기 전용입니다.

컨텍스트 변수 사용

트리거를 발생한 레코드에 액세스하려면 컨텍스트 변수를 사용합니다. 예를 들어 Trigger.new에는 트리거 삽입 또는 업데이트에 삽입된 모든 레코드가 포함됩니다. Trigger.old에서 트리거 업데이트에 업데이트되기 전의 sObject 이전 버전 또는 트리거 삭제에서 삭제된 sObject 목록을 제공합니다. 하나의 레코드가 삽입되거나 API 또는 Apex를 통해 많은 레코드가 대량으로 삽입될 경우 트리거가 발생할 수 있습니다. 따라서 Trigger.new와 같은 컨텍스트 변수에 하나의 레코드만 또는 여러 레코드를 포함할 수 있습니다. Trigger.new를 반복하여 각 개별 sObject를 가져올 수 있습니다.

이 예제는 HelloWorldTrigger 예시 트리거의 수정된 버전입니다. for 루프에서 각 계정을 반복하고 각각에 대한 Description(설명) 필드를 업데이트합니다.

trigger HelloWorldTrigger on Account (before insert) {
    for(Account a : Trigger.new) {
        a.Description = 'New description';
    }
}
참고

시스템은 트리거 실행이 완료된 후 이전 트리거를 실행한 레코드를 저장합니다. DML 삽입 또는 업데이트 작업을 명시적으로 호출하지 않고 트리거의 레코드를 수정할 수 있습니다. 해당 레코드에 대해 DML 문을 수행하면 오류가 발생합니다.

일부 다른 컨텍스트 변수는 업데이트 또는 기타 이벤트로 인해 트리거가 실행되었는지 여부를 나타내는 부울 값을 반환합니다. 이러한 변수는 트리거가 여러 이벤트를 결합할 때 유용합니다. 다음의 예를 확인해 보세요.

trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
            // Process before insert
        } else if (Trigger.isAfter) {
            // Process after insert
        }
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}

다음 표는 트리거에 사용할 수 있는 모든 컨텍스트 변수의 포괄적인 목록입니다.

변수

사용

isExecuting

Apex 코드의 현재 컨텍스트가 Visualforce 페이지, 웹 서비스 또는 executeanonymous() API 호출이 아닌 트리거인 경우 true를 반환합니다.

isInsert

Salesforce 사용자 인터페이스, Apex 또는 API에서 삽입 작업으로 인해 이 트리거가 실행된 경우 true를 반환합니다.

isUpdate

Salesforce 사용자 인터페이스, Apex 또는 API에서 업데이트 작업으로 인해 이 트리거가 실행된 경우 true를 반환합니다.

isDelete

Salesforce 사용자 인터페이스, Apex 또는 API에서 삭제 작업으로 인해 이 트리거가 실행된 경우 true를 반환합니다.

isBefore

레코드가 저장되기 전에 이 트리거가 실행된 경우 true를 반환합니다.

isAfter

모든 레코드가 저장된 후에 이 트리거가 실행된 경우 true를 반환합니다.

isUndelete

Recycle Bin(휴지통)에서 레코드를 복구한 후 이 트리거가 실행된 경우 true를 반환합니다. 이 복구는 Salesforce 사용자 인터페이스, Apex 또는 API에서 삭제 취소 작업 후에 발생할 수 있습니다.

new

sObject 레코드의 새 버전 목록을 반환합니다.

이 sObject 목록은 insert, updateundelete 트리거에서만 사용할 수 있으며 레코드는 before 트리거에만 수정할 수 있습니다.

newMap

sObject 레코드의 새 버전에 대한 ID 지도입니다.

이 지도는 before update, after insert, after updateafter undelete 트리거에서만 사용할 수 있습니다.

old

sObject 레코드의 이전 버전 목록을 반환합니다.

이 sObject 목록은 update and delete 트리거에서만 사용할 수 있습니다.

oldMap

sObject 레코드의 이전 버전에 대한 ID 지도입니다.

이 지도는 updatedelete 트리거에서만 사용할 수 있습니다.

operationType

현재 작업에 해당하는 System.TriggerOperation 유형의 열거형을 반환합니다.

System.TriggerOperation 열거형의 가능한 값은 다음과 같습니다. BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE, AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, 및 AFTER_UNDELETE. 다양한 트리거 유형에 따라 프로그래밍 논리를 변경하는 경우 고유한 트리거 실행 열거 상태의 순열이 다른 switch 문을 사용해 보세요.

size

이전 및 새 트리거 호출의 총 레코드 수입니다.

트리거에서 클래스 메서드 호출하기

트리거에서 공용 유틸리티 메서드를 호출할 수 있습니다. 다른 클래스의 메서드를 호출하면 코드 재사용이 가능하고 트리거 크기가 줄어들며 Apex 코드의 유지 관리가 향상됩니다. 또한 개체 지향 프로그래밍을 사용할 수 있습니다.

다음 예제 트리거는 트리거에서 정적 메서드를 호출하는 방법을 보여줍니다. 삽입 이벤트로 인해 트리거가 실행된 경우 예제에서 EmailManager 클래스에 대한 정적 sendMail() 메서드를 호출합니다. 이 유틸리티 메서드는 지정된 수신자에게 이메일을 전송하고 삽입된 연락처 레코드 수를 포함합니다.

  1. Developer Console에서 File(파일) | New(새로 만들기) | Apex Class(Apex 클래스)를 클릭합니다.
  2. EmailManager를 입력하고 OK(확인)를 클릭합니다.
  3. 기본 클래스 본문을 아래에 제시된 EmailManager 클래스 예시로 변경합니다.
    public class EmailManager {
        // Public method
        public static void sendMail(String address, String subject, String body) {
            // Create an email message object
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {address};
            mail.setToAddresses(toAddresses);
            mail.setSubject(subject);
            mail.setPlainTextBody(body);
            // Pass this email message to the built-in sendEmail method
            // of the Messaging class
            Messaging.SendEmailResult[] results = Messaging.sendEmail(
                                      new Messaging.SingleEmailMessage[] { mail });
            // Call a helper method to inspect the returned results
            inspectResults(results);
        }
        // Helper method
        private static Boolean inspectResults(Messaging.SendEmailResult[] results) {
            Boolean sendResult = true;
            // sendEmail returns an array of result objects.
            // Iterate through the list to inspect results.
            // In this class, the methods send only one email,
            // so we should have only one result.
            for (Messaging.SendEmailResult res : results) {
                if (res.isSuccess()) {
                    System.debug('Email sent successfully');
                }
                else {
                    sendResult = false;
                    System.debug('The following errors occurred: ' + res.getErrors());
                }
            }
            return sendResult;
        }
    }
  4. Developer Console에서 File(파일) | New(새로 만들기) | Apex Trigger(Apex 트리거)를 클릭합니다.
  5. 트리거 이름에 ExampleTrigger를 입력한 다음 sObject에 대한 Contact(연락처)를 선택합니다. Submit(제출)을 클릭합니다.
  6. 기본 코드를 다음으로 바꾼 다음 sendMail()의 이메일 주소 자리 표시자 텍스트를 이메일 주소로 수정합니다.
    trigger ExampleTrigger on Contact (after insert, after delete) {
        if (Trigger.isInsert) {
            Integer recordCount = Trigger.new.size();
            // Call a utility method from another class
            EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial',
                        recordCount + ' contact(s) were inserted.');
        }
        else if (Trigger.isDelete) {
            // Process after delete
        }
    }
  7. 저장하려면 Ctrl+S를 누릅니다.
  8. 트리거를 테스트하려면 연락처를 만듭니다.
    1. Debug(디버그) | Open Execute Anonymous Window(익명 실행 창 열기)를 클릭합니다.
    2. 새 창에서 다음을 추가한 다음 Execute(실행)를 클릭합니다.
      Contact c = new Contact(LastName='Test Contact');
      insert c;
  1. 디버그 로그에서 트리거가 실행되었는지 확인합니다. 로그 끝 부분에서 유틸리티 메서드(DEBUG|Email sent successfully)로 작성된 디버그 메시지를 찾습니다.
  2. 이제 본문 텍스트 1 contact(s) were inserted(1개의 연락처가 삽입되었습니다)가 포함된 이메일을 받았는지 확인하세요.

    새 트리거가 적용되면 연락처를 한 개 이상 추가할 때마다 이메일이 전송됩니다!

트리거는 종종 트리거 컨텍스트의 레코드(이 트리거를 발생시킨 레코드)와 관련된 레코드에 액세스하고 관리하는 데 사용됩니다.

이 트리거는 기회가 이미 계정과 연결되어 있지 않은 경우 새 계정 또는 업데이트된 각 계정에 대한 관련 기회를 추가합니다. 트리거는 먼저 SOQL 쿼리를 수행하여 트리거가 실행된 계정에 대한 모든 하위 기회를 가져옵니다. 다음으로 트리거는 Trigger.new의 sObject 목록을 반복하여 각 계정 sObject를 가져옵니다. 계정에 관련된 기회 sObject가 없는 경우 for 루프는 기회를 만듭니다. 트리거가 새로운 기회를 생성한 경우 최종 명령문이 이를 삽입합니다.

  1. Developer Console을 사용하여 다음 트리거를 추가합니다(HelloWorldTrigger 예제의 단계를 따르지만 트리거 이름에 대해 AddRelatedRecord를 사용합니다).
    trigger AddRelatedRecord on Account(after insert, after update) {
        List<Opportunity> oppList = new List<Opportunity>();
        // Get the related opportunities for the accounts in this trigger
        Map<Id,Account> acctsWithOpps = new Map<Id,Account>(
            [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.new]);
        // Add an opportunity for each account if it doesn't already have one.
        // Iterate through each account.
        for(Account a : Trigger.new) {
            System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size());
            // Check if the account already has a related opportunity.
            if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) {
                // If it doesn't, add a default opportunity
                oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                           StageName='Prospecting',
                                           CloseDate=System.today().addMonths(1),
                                           AccountId=a.Id));
            }
        }
        if (oppList.size() > 0) {
            insert oppList;
        }
    }
  2. 트리거를 테스트하려면 Salesforce 사용자 인터페이스에서 계정을 만들고 Apples & Oranges(사과 및 오렌지)로 이름을 지정합니다.
  3. 계정 페이지의 기회 관련 목록에서 새 기회를 찾습니다. 트리거가 이 기회를 자동으로 추가했습니다.
참고

추가한 트리거는 트리거 컨텍스트의 일부인 모든 레코드, 즉 Trigger.new를 반복하는 for 루프를 반복합니다. 그러나 이 트리거의 루프가 더 효율적일 수 있습니다. 이 트리거 컨텍스트에서 모든 계정에 실제로 액세스할 필요는 없지만 하위 집합, 즉 기회가 없는 계정에만 액세스할 필요가 있습니다. 다음 유닛에서는 이 트리거를 보다 효율적으로 만드는 방법을 보여줍니다. 대량 트리거 디자인 패턴 유닛에서 기회가 없는 계정만 가져오도록 SOQL 쿼리를 수정하는 방법을 알아봅니다. 그런 다음 해당 레코드에 대해서만 반복하는 방법을 배웁니다.

트리거 예외 사용하기

특정 조건이 충족되면 레코드가 저장되지 않도록 하는 것과 같이 특정 데이터베이스 작업에 대한 제한을 추가해야 하는 경우가 있습니다. 트리거에 레코드가 저장되지 않도록 하려면 문제가 되는 sObject에 대한 addError() 메서드를 호출합니다. addError() 메서드는 트리거 내부에서 치명적인 오류를 발생시킵니다. 오류 메시지는 사용자 인터페이스에 표시되고 기록됩니다.

다음 트리거는 관련 기회가 있는 경우 계정이 삭제되는 것을 방지합니다. 기본적으로 계정을 삭제하면 모든 관련 레코드가 계단식으로 삭제됩니다. 이 트리거는 기회의 계단식 삭제를 방지합니다. 이 트리거를 직접 사용해 보세요. 이전 예제를 실행한 경우 조직에 관련 기회가 있는 Apples & Oranges라는 계정이 있습니다. 이 예제에서 해당 샘플 계정을 사용합니다.

  1. Developer Console을 사용하여 다음 트리거를 추가합니다.
    trigger AccountDeletion on Account (before delete) {
        // Prevent the deletion of accounts if they have related opportunities.
        for (Account a : [SELECT Id FROM Account
                         WHERE Id IN (SELECT AccountId FROM Opportunity) AND
                         Id IN :Trigger.old]) {
            Trigger.oldMap.get(a.Id).addError(
                'Cannot delete account with related opportunities.');
        }
    }
  2. Salesforce 사용자 인터페이스에서 Apples & Oranges 계정 페이지로 이동하여 Delete(삭제)를 클릭합니다.
  3. 확인 팝업창에서 OK(확인)를 클릭합니다.

    Cannot delete account with related opportunities(관련 기회가 있는 계정을 삭제할 수 없습니다)라는 맞춤형 오류 메시지로 유효성 검사 오류를 찾습니다.

  4. AccountDeletion 트리거를 비활성화합니다. 이 트리거를 활성 상태로 두면 과제를 확인할 수 없습니다.
    1. Setup(설정)에서 Apex Triggers를 검색합니다.
    2. Apex Triggers(Apex 트리거) 페이지에서 AccountDeletion 트리거 옆에 있는 Edit(편집)을 클릭합니다.
    3. Is Active(활성)를 선택 해제합니다.
    4. Save(저장)를 클릭합니다.
참고

트리거에서 addError()를 호출하면 대량 DML이 부분적으로 성공하여 호출된 경우를 제외하고 전체 작업 집합이 롤백되도록 합니다.

  • Lightning Platform API의 대량 DML 호출이 트리거를 생성한 경우 런타임 엔진이 잘못된 레코드를 따로 설정합니다. 그런 다음 런타임 엔진은 오류를 생성하지 않은 레코드를 부분적으로 저장해 봅니다.
  • Apex의 DML 문이 트리거를 생성한 경우 모든 오류가 전체 작업을 롤백합니다. 그러나 런타임 엔진은 여전히 작업의 모든 레코드를 처리하여 포괄적인 오류 목록을 컴파일합니다.

트리거 및 콜아웃

Apex를 사용하면 외부 웹 서비스를 호출하고 Apex 코드를 외부 웹 서비스와 통합할 수 있습니다. 외부 웹 서비스에 대한 Apex 호출을 콜아웃이라고 합니다. 예를 들어 주식 시세 서비스에 콜아웃을 만들어 최신 시세를 얻을 수 있습니다. 트리거에서 호출할 경우 호출은 트리거 프로세스가 외부 서비스의 응답을 기다리는 동안 작업을 차단하지 않도록 비동기적으로 수행되어야 합니다. 비동기 호출은 백그라운드 프로세스에서 수행되며 외부 서비스를 반환할 경우 응답이 수신됩니다.

트리거에서 콜아웃을 만들려면 비동기적으로 실행되는 클래스 메서드를 호출합니다. 이러한 메서드를 future 메서드라고 하며 @future(callout=true) 주석을 추가합니다. 이 예제 클래스에는 콜아웃을 만드는 future 메서드가 포함되어 있습니다.

참고

이 예제에서는 설명 목적으로만 가상의 엔드포인트 URL을 사용합니다. 끝점을 유효한 URL로 변경하고 엔드포인트에 대해 Salesforce에 원격 사이트를 추가하지 않으면 이 예제를 실행할 수 없습니다.

public class CalloutClass {
    @future(callout=true)
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // Set the endpoint URL.
        String endpoint = 'http://yourHost/yourService';
        request.setEndPoint(endpoint);
        // Set the HTTP verb to GET.
        request.setMethod('GET');
        // Send the HTTP request and get the response.
        HttpResponse response = new HTTP().send(request);
    }
}

이 예제는 콜아웃을 비동기적으로 만들기 위해 클래스의 메서드를 호출하는 트리거를 보여줍니다.

trigger CalloutTrigger on Account (before insert, before update) {
    CalloutClass.makeCallout();
}

이 섹션에서는 콜아웃의 개요만 제공하며 콜아웃을 자세히 다루지는 않습니다. 자세한 내용은 Apex 개발자 가이드의 Apex를 사용하여 콜아웃 호출을 참조하세요.

리소스

Salesforce 도움말에서 Trailhead 피드백을 공유하세요.

Trailhead에 관한 여러분의 의견에 귀 기울이겠습니다. 이제 Salesforce 도움말 사이트에서 언제든지 새로운 피드백 양식을 작성할 수 있습니다.

자세히 알아보기 의견 공유하기