1.비동기를 사용할 경우 사용자 효율성, 확장성, 처리 제한 뛰어넘기 등이 가능하다.
Type | Overview | Common Scenarios |
Future Methods | 자체 스레드에서 실행되고 사용 가능할 때까지 대기 | Web service callout. |
Batch Apex | 일반 처리 제한을 초과하는 대규모 작업을 실행 | Data cleansing or archiving of records. |
Queueable Apex | Future Methods와 유사하지만 추가 작업 연결을 제공하고 더 복잡한 데이터 유형을 사용할 수 있다. | Performing sequential processing operations with external Web services. |
Scheduled Apex | Apex를 지정한 시간에 실행 | Daily or weekly tasks. |
2.future 메서드는 정적이며 void type만 반환할 수 있으며
표준 개체 또는 커스텀 개체를 사용할 수 없기 때문에
비동기적으로 처리하고 싶은 레코드 id의 메서드를 매개변수로 전달해야 한다.
public class SomeClass {
@future
public static void someFutureMethod(List<Id> recordIds) {
List<Account> accounts = [Select Id, Name from Account Where Id IN :recordIds];
// process account records to do awesome stuff
}
}
또한 비동기 처리는 “순서를 보장받을 수 없기 때문에” 동시에 future 메서드가 실행될 수 있고
독립적으로 구분하지 않은 이상 하나의 레코드에 동시에 간섭해 에러가 발생할 수 있다.
주의사항으로는
여러개의 future 메서드를 사용하는 것 보다는 하나의 future 내부에
여러 callout을 묶어 사용하는 것을 권장하며
대규모로 진행할 경우 테스트를 통해 처리가 지연되지 않고 제대로 되는지 확인하는 것이 좋고
대규모 처리가 필요할 경우에는 future 대신 Batch Apex를 사용하는 것이 좋다.
3.future 과제 코드
//class
public class AccountProcessor {
@future
public static void countContacts(List<Id> accIds){
//(select id from contacts )부분은 효율을 위해서 contacts를 안한 느낌
List<Account> accounts = [Select Id, Number_of_Contacts__c, (select id from contacts )
from Account Where Id IN :accIds];
for( Account acc : accounts ) {
List<Contact> contatcs = acc.contacts ;
acc.Number_of_Contacts__c = contatcs.size();
}
update accounts;
}
}
//test class
@IsTest
public class AccountProcessorTest {
@IsTest
static void test(){
//테스트용 account 생성
Account acc1 = new Account();
acc1.Name = 'Test Account';
Insert acc1;
//테스트용 account에 contact 연결
Contact cont = New Contact();
cont.FirstName ='Bob';
cont.LastName ='Masters';
cont.AccountId = acc1.Id;
Insert cont;
//테스트에 사용할 account.id list 생성
List<Id> setAccId = new List<ID>();
setAccId.add(acc1.id);
//테스트 진행
Test.startTest();
AccountProcessor.countContacts(setAccId);
Test.stopTest();
//결과 확인
Account ACC = [select Number_of_Contacts__c from Account where id = :acc1.id LIMIT 1];
System.assertEquals ( Integer.valueOf(ACC.Number_of_Contacts__c) ,1);
}
}
4.batch apex를 사용하기 위해서는 Database.Batchable 인터페이스를 구현하고
start, execute, finish의 세 가지 메서드를 포함해야 한다.
start에서는 처리 전 레코드 또는 개체 수집을 위해 사용되며
Database.QueryLocator 또는 개체를 반환한다.
QueryLocator를 통해 SOQL을 진행할 경우 최대 5천만개의 레코드를 조회할 수 있지만
Iterable등을 사용할 경우에는 기존 제한과 동일하게 사용된다.
execute는 start에서 수집한 데이터를 실제 처리하는 곳으로
배치의 처리 단위는 200이며 순서대로 처리하는 것이 보장되지 않는데
테스트에서는 하나의 배치 처리 단위인 200개 까지만 테스트해볼 수 있으며
보통 List<sObject> 형태로 매개변수를 사용한다.
finish는 execute가 종료된 이후 한번 호출되며
이메일 발송, 처리 내역 안내 등의 사후 처리 작업에 사용된다.
Batch Apex는 아래와 같은 형태로 작동한다.
public class MyBatchClass implements Database.Batchable<sObject> {
public (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
// collect the batches of records or objects to be passed to execute
}
public void execute(Database.BatchableContext bc, List<P> records){
// process each batch of records
}
public void finish(Database.BatchableContext bc){
// execute any post-processing operations
}
}
5.배치 클래스를 호출해야 할 경우 아래와 같이 인스턴스 후
Database.executeBatch를 사용해 호출할 수 있으며
MyBatchClass myBatchObject = new MyBatchClass();
Id batchId = Database.executeBatch(myBatchObject);
추가적인 매개변수 입력을 통해 배치 크기를 제한할 수도 있고
Id batchId = Database.executeBatch(myBatchObject, 100);
AsyncApexJob을 사용해 현재 작업을 추적할 수 있다.
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];
6.@testSetup 어노테이션을 사용해서 데이터를 변경할 경우
해당 테스트 클래스 내의 다른 메서드들도 이 데이터에 안정적으로 조회할 수 있기 때문에
테스트를 위한 데이터 수정이 공통적인 경우 @testSetup 어노테이션을 사용하는 것이 좋다.
단 하나의 테스트에만 사용되는 경우라고 하더라도
공통된 데이터를 사용한 메서드 확장이 있을 수 있는 경우에는
@testSetup 어노테이션을 사용하는 것이 권장된다.
7.batch 과제 코드
//class
public class LeadProcessor implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT ID, LeadSource FROM Lead'
);
}
public void execute(Database.BatchableContext bc, List<Lead> scope){
// process each batch of records
List<Lead > Leads = new List<Lead >();
for (Lead Lead : scope) {
Lead.LeadSource = 'Dreamforce';
// add contact to list to be updated
Leads.add(Lead);
}
update Leads;
}
public void finish(Database.BatchableContext bc){
// execute any post-processing operations
}
}
//testClass
@IsTest
public class LeadProcessorTest {
@testSetup
static void setup() {
List<Lead> Leads = new List<Lead>();
// insert 200 Lead
for (Integer i=0;i<200;i++) {
Leads.add(new Lead(LastName = 'LN', Company = 'C' , LeadSource ='Lead '+i));
}
insert Leads;
}
@isTest static void test() {
Test.startTest();
LeadProcessor LeadProcess = new LeadProcessor();
Id batchId = Database.executeBatch(LeadProcess);
Test.stopTest();
// after the testing stops, assert records were updated properly
System.assertEquals(200, [select count() from Lead where LeadSource = 'Dreamforce']);
}
}
8.Queueable Apex는 future보다 개선된 방식으로 future와 Batch Apex가 혼합되어있다.
여러 타입의 데이터 변수를 사용할 수 있으며 모니터링을 쉽게 할 수 있고
작업 연결 기능을 통해 순차적인 실행을 보장할 수 있다.
연결 작업은 아래와 같이 추가할 수 있다.
public class FirstJob implements Queueable {
public void execute(QueueableContext context) {
// Awesome processing logic here
// Chain this job to next job by submitting the next job
System.enqueueJob(new SecondJob());
}
}
9.clone의 경우 아래와 같이 4개의 매개변수를 받는다.
clone(preserveId, isDeepClone, preserveReadonlyTimestamps, preserveAutonumber)
이름에서도 대충 알 수 있지만 4가지 모두 boolean type이며
preserveId는 id까지 복제(레코드 증식의 경우 false가 맞다)되고
API version 22.0 or earlier version에서는 기본값이 true기 때문에 id가 보존,
isDeepClone는 깊은복사(참조가 아닌 복사)여부,
preserveReadonlyTimestamps는 (CreatedById , CreatedDate , LastModifiedById, LastModifiedDate)를 의미하며 새로 생성됬음을 표시하기위해서는 flase를 사용해야 하고
preserveAutonumber는 auto number 보존 여부인데 이건 잘 모르겠다.
10.테스트가 개판이지만 통과된 코드
//class
public class AddPrimaryContact implements Queueable {
private Contact contact;
private String stateVar;
public AddPrimaryContact(Contact records, String stateVar) {
this.contact = records;
this.stateVar = stateVar;
}
public void execute(QueueableContext context) {
List<Account> accounts = [SELECT Id, BillingState
FROM Account WHERE BillingState = :stateVar LIMIT 200];
List<Contact> contactsToInsert = new List<Contact>();
for (Account account : accounts) {
Contact clonedContact = contact.clone(false, true, false, false);
clonedContact.AccountId = account.Id;
contactsToInsert.add(clonedContact);
}
update contactsToInsert;
}
}
//test class
@isTest
public class AddPrimaryContactTest {
@testSetup
static void setup() {
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 50; i++) {
accounts.add(new Account(name='Test Account'+i, BillingState='NY'));
accounts.add(new Account(name='Test Account'+i, BillingState='CA'));
}
insert accounts;
}
static testmethod void testQueueable() {
// query for test data to pass to queueable class
Contact contact = new Contact(LastName='TestRyu');
// Create our Queueable instance
AddPrimaryContact addContact = new AddPrimaryContact(contact, 'CA');
// startTest/stopTest block to force async processes to run
Test.startTest();
System.enqueueJob(addContact);
Test.stopTest();
// Validate the job ran. Check if record have correct parentId now
System.assertEquals(50, [select count() from Contact where LastName='TestRyu']);
}
}
11.System.schedule()를 사용해 클래스를 실행할 경우 권한을 무시하고 실행하기 때문에
대량 업데이트, 가져오기 마법사, 인터페이스 대량 변경 등의 경우를 고려해야 한다.
12.Schedule 비동기를 사용한 코드
//class
public class DailyLeadProcessor implements Schedulable {
public void execute(SchedulableContext ctx) {
List<Lead> leads = [SELECT Id, LeadSource FROM Lead WHERE LeadSource = ''];
if(leads.size() > 0){
List<Lead> updateLeads = new List<Lead>();
for(Lead lead : leads){
lead.LeadSource = 'DreamForce';
updateLeads.add(lead);
}
update updateLeads;
}
}
}
//test class
@IsTest
public class DailyLeadProcessorTest {
//Seconds Minutes Hours Day_of_month Month Day_of_week optional_year
public static String CRON_EXP = '0 0 0 17 4 ? 2024';
static testmethod void testScheduledJob(){
List<Lead> leads = new List<Lead>();
for(Integer i = 0; i < 200; i++){
Lead lead = new Lead(LastName = 'Test ' + i, LeadSource = '', Company = 'Test Company ' + i);
leads.add(lead);
}
insert leads;
Map<Id, Lead> leadMap = new Map<Id, Lead>(leads);
List<Id> leadId = new List<Id>(leadMap.keySet());
Test.startTest();
// Schedule the test job
String jobId = System.schedule('Update LeadSource to DreamForce', CRON_EXP, new DailyLeadProcessor());
Test.stopTest();
System.assertEquals(200, [select count() from Lead WHERE LeadSource='DreamForce']);
}
}
(1).백준 14215번 세 막대는 랜덤으로 길이가 주어진 세 막대가 있을 때
삼각형의 둘레가 최대가 되는 지점을 출력해야 하는 문제로
만약 삼각형이 되지 않을 경우 막대들의 길이를 마음대로 줄일 수 있으며
막대의 길이는 자연수(양의 정수라고 표기되어 있다)가 되어야 헀다.
사실 삼각형 문제들은 a<=b<=c일 때 a+b>c인 조건이 되어야만 삼각형일 수 있으므로
그 외의 조건에는 c를 a+b-1로 줄이는 것 외에는 해답이 없기 때문에
a,b,c의 길이비교 후 if문으로 분기처리를 했다.
const input = `1 100 1`.split(' ').map(Number).sort((a,b) => a-b)
if(input[0] + input[1] <= input[2]){
console.log((input[0]+input[1])*2 -1)
}
else{
console.log(input[0] + input[1] + input[2])
}
'회고' 카테고리의 다른 글
[수습일지] - 24 (0) | 2023.04.19 |
---|---|
[수습일지] - 23 (0) | 2023.04.18 |
[수습일지] - 21(주말) (0) | 2023.04.16 |
[수습일지] - 20(주말) (0) | 2023.04.15 |
[수습일지] - 19 (0) | 2023.04.14 |