Trigger.new vs Trigger.old — Apex 트리거를 올바르게 이해하기
Source: Dev.to
번역을 진행하려면 번역하고자 하는 전체 텍스트를 제공해 주세요. 텍스트를 주시면 요청하신 대로 한국어로 번역해 드리겠습니다.
소개
Apex 트리거를 배울 때 핵심은 Trigger.new와 Trigger.old를 언제 사용해야 하는지 이해하는 것입니다.
- Trigger.new – 현재 Salesforce가 저장하려고 하는 데이터입니다.
- Trigger.old – 변경 전 존재하던 데이터입니다.
이 구분을 염두에 두면 대부분의 사용 시나리오가 논리적으로 이해됩니다.
Trigger.new
무엇을 나타내는가
Trigger.new는 Salesforce가 삽입하거나 업데이트하려고 하는 현재 또는 들어오는 값을 포함합니다.
일반적인 사용 사례
- 검증
- 필드 값 설정 또는 수정
- 업데이트된 값 읽기
- 새로운 데이터를 기반으로 관련 레코드 생성
다음 상황에서 사용 가능
| 시나리오 | 사용 가능? |
|---|---|
| Insert | ✅ |
| Update | ✅ |
| Undelete | ✅ |
Undelete: 이 트리거 컨텍스트는 레코드가 휴지통에서 복원될 때 실행되며, Trigger.new에는 복구되는 레코드 값이 들어 있습니다.
예시: 검증
for (Contact con : Trigger.new) {
if (con.Email == null) {
con.Email.addError('Email is required');
}
}
검증이 작동하는 이유는 Trigger.new가 그 순간 저장되는 데이터를 나타내기 때문입니다.
Trigger.old
의미
Trigger.old는 변경 전 레코드의 스냅샷입니다.
일반적인 사용 사례
- 필드 변경 감지
- 이전 값에 기반한 조건 로직
- 중복 작업 방지
사용 가능한 상황
| 시나리오 | 사용 가능 여부 |
|---|---|
| 업데이트 | ✅ |
| 삭제 | ✅ |
예시: 변경 감지
for (Opportunity newOpp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(newOpp.Id);
if (newOpp.StageName != oldOpp.StageName) {
// Stage has changed
}
}
Trigger.old는 비교를 위한 이전 상태를 제공합니다.
예시: 의미 있는 업데이트 감지
for (Opportunity newOpp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(newOpp.Id);
if (newOpp.StageName == 'Closed Won' &&
oldOpp.StageName != 'Closed Won') {
// Stage just changed to Closed Won
}
}
팁
- Trigger.oldMap 및 Trigger.newMap은 Id(
Id → Record)로 레코드에 빠르게 접근할 수 있게 해줍니다. - 대량 작업에서 기존 값과 새 값을 비교할 때는 루프보다 맵을 사용하는 것이 선호됩니다.
- 로직이 단순히 현재 값이 아니라 변화를 감지해야 할 경우,
Trigger.new와Trigger.old모두 필요합니다.
컨텍스트 기반 결정표
| 요구 사항 | 사용 |
|---|---|
| 입력 검증 | Trigger.new |
| 필드 수정 | Trigger.new |
| 이전 vs 새 비교 | Trigger.new + Trigger.old |
| 삭제 전 정리 | Trigger.old |
| 레코드 저장 차단 | Trigger.new.addError() |
트리거 유형
| 트리거 유형 | 수행 작업 |
|---|---|
| Before | Trigger.new 수정 |
| After | Trigger.new 읽기, Trigger.old와 비교 |
중요: After 트리거에서는 레코드가 이미 데이터베이스에 커밋되었기 때문에
Trigger.new를 사용해 레코드를 수정할 수 없습니다. 이 단계에서Trigger.new는 읽기 전용이며, 추가적인 변경은 별도의 DML 작업이 필요합니다.
예시: DML을 사용해 after 트리거에서 업데이트
List<Account> accsToUpdate = new List<Account>();
for (Account acc : Trigger.new) {
accsToUpdate.add(new Account(
Id = acc.Id,
Name = 'Updated Name'
));
}
update accsToUpdate;
트리거 컨텍스트 개요
| 컨텍스트 | 사용 가능한 데이터 | 목적 |
|---|---|---|
| Before Insert | Trigger.new | 레코드가 아직 존재하지 않음; 저장 전에 필드를 수정 |
| Before Update | Trigger.new + Trigger.old | 이전과 새 데이터를 비교; 저장 전에 필드를 수정 |
| After Update | Trigger.new + Trigger.old | 레코드가 저장됨; 읽기 전용, 변경 사항 감지 |
| Before Delete | Trigger.old | 삭제될 레코드; 정리 로직 |
| After Delete | Trigger.old | 레코드가 삭제됨; 읽기 전용, 감사 또는 관련 로직 |
데이터 타임라인을 시각화하면 올바른 컨텍스트를 선택하는 것이 자연스럽습니다. 규칙을 외우는 대신 다음과 같이 질문하세요:
“나는 새로운 데이터, 이전 데이터, 혹은 그 사이의 변경을 다루고 있나요?”
답을 얻으면 적절한 컨텍스트(Trigger.new, Trigger.old 또는 둘 다)가 명확해집니다.
모범 사례
- Bulkify 트리거 – 단일 레코드 대신
Trigger.new와 같은 컬렉션을 반복하여 거버너 제한을 피합니다. Trigger.newMap및Trigger.oldMap을 사용해 Id 기반 효율적인 조회를 수행하고 중첩 루프를 피합니다.