Skip to main content
10๋ถ„๋งŒ ์‹œ๊ฐ„์„ ๋‚ด์–ด ์ปค๋ฎค๋‹ˆํ‹ฐ ์„ค๋ฌธ์กฐ์‚ฌ์— ์ฐธ์—ฌํ•ด ์ฃผ์„ธ์š”. ์ง€๊ธˆ๋ถ€ํ„ฐ 7์›” 19์ผ๊นŒ์ง€ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ฐธ์—ฌํ•˜์‹œ๋ ค๋ฉด ์—ฌ๊ธฐ๋ฅผ ํด๋ฆญํ•ด ์ฃผ์„ธ์š”.

๋Œ€๋Ÿ‰ Apex ํŠธ๋ฆฌ๊ฑฐ

ํ•™์Šต ๋ชฉํ‘œ

์ด ์œ ๋‹›์„ ์™„๋ฃŒํ•˜๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • sObject ์ปฌ๋ ‰์…˜์—์„œ ์ž‘๋™ํ•˜๋Š” ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํšจ์œจ์ ์ธ SOQL ๋ฐ DML ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ฐธ๊ณ 

์ฐธ๊ณ 

ํ•œ๊ตญ์–ด๋กœ ํ•™์Šตํ•˜์‹œ๊ฒ ์–ด์š”? Trailhead playground์—์„œ ํ•œ๊ตญ์–ด๋กœ ์‹ค์Šต ๊ณผ์ œ๋ฅผ ์‹œ์ž‘ํ•˜๊ณ , ๊ด„ํ˜ธ ์•ˆ์— ์ œ๊ณต๋œ ๋ฒˆ์—ญ์„ ์‚ฌ์šฉํ•ด ํƒ์ƒ‰ํ•ด ๋ณด์„ธ์š”. ์˜์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹ค์Šต ๊ณผ์ œ ๊ฒ€์ฆ์ด ์ด๋ฃจ์–ด์ง€๋ฏ€๋กœ ์˜๋ฌธ์œผ๋กœ ํ‘œ์‹œ๋œ ๊ฐ’๋งŒ ๋ณต์‚ฌํ•ด ๋ถ™์—ฌ ๋„ฃ์Šต๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด ์กฐ์ง์—์„œ ์‹ค์Šต ๊ณผ์ œ๋ฅผ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ, (1) ๋กœ์บ˜์„ ๋ฏธ๊ตญ์„ ๋ฐ”๊พธ๊ณ  (2) ์—ฌ๊ธฐ์— ์ œ์‹œ๋œ ์ง€์นจ์— ๋”ฐ๋ผ ์–ธ์–ด๋ฅผ ์˜์–ด๋กœ ๋ฐ”๊พผ ํ›„ (3) "Check Challenge(๊ณผ์ œ ํ™•์ธ)" ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋‹ค์‹œ ์ง„ํ–‰ํ•ด ๋ณด์„ธ์š”.

์›ํ•˜๋Š” ์–ธ์–ด๋กœ Trailhead ์‚ฌ์šฉํ•˜๊ธฐ ๋ฑƒ์ง€๋ฅผ ํ™•์ธํ•ด ํ˜„์ง€ํ™”๋œ Trailhead ๊ฒฝํ—˜์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.

๋Œ€๋Ÿ‰ ํŠธ๋ฆฌ๊ฑฐ ๋””์ž์ธ ํŒจํ„ด

Apex ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋Œ€๋Ÿ‰์œผ๋กœ ์ž‘๋™ํ•˜๋Š” ๋ฐ ์ตœ์ ํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠธ๋ฆฌ๊ฑฐ์—์„œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋Œ€๋Ÿ‰ ๋””์ž์ธ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋Œ€๋Ÿ‰ ๋””์ž์ธ ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ํŠธ๋ฆฌ๊ฑฐ์˜ ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋˜๊ณ  ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰์ด ์ค„์–ด ํ”Œ๋žซํผ ์ œํ•œ์„ ์ดˆ๊ณผํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ์•„์ง‘๋‹ˆ๋‹ค.

์ฝ”๋“œ๋ฅผ ๋Œ€๋Ÿ‰์œผ๋กœ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ ๋Œ€๋Ÿ‰์œผ๋กœ ์ฒ˜๋ฆฌ๋œ ์ฝ”๋“œ๊ฐ€ ๋งŽ์€ ์ˆ˜์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  Salesforce Platform์˜ ๊ฑฐ๋ฒ„๋„ˆ ์ œํ•œ ๋‚ด์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฑฐ๋ฒ„๋„ˆ ์ œํ•œ์€ ๋Ÿฐ์–ด์›จ์ด ์ฝ”๋“œ๊ฐ€ ๋‹ค์ค‘ ํ…Œ๋„ŒํŠธ ํ”Œ๋žซํผ์—์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋…์ ํ•˜์ง€ ์•Š๋„๋ก ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ์„น์…˜์—์„œ๋Š” ํŠธ๋ฆฌ๊ฑฐ์˜ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ์—์„œ ์ž‘๋™ํ•˜๊ณ  ๋‹จ์ผ sObject ๋Œ€์‹  sObject ์ปฌ๋ ‰์…˜์—์„œ SOQL ๋ฐ DML์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋“ฑ, ํŠธ๋ฆฌ๊ฑฐ์—์„œ Apex ์ฝ”๋“œ๋ฅผ ๋Œ€๋Ÿ‰์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฃผ์š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. SOQL ๋ฐ DML ๋Œ€๋Ÿ‰ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋Š” ํด๋ž˜์Šค์˜ SOQL ๋ฐ DML์„ ํฌํ•จํ•˜์—ฌ ๋ชจ๋“  Apex ์ฝ”๋“œ์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ์˜ˆ์ œ๋Š” ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ Trigger.new ์ปจํ…์ŠคํŠธ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ ˆ์ฝ”๋“œ ์ง‘ํ•ฉ์— ๋Œ€ํ•ด ์ž‘์—…ํ•˜๊ธฐ

๋จผ์ € ํŠธ๋ฆฌ๊ฑฐ์—์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋Œ€๋Ÿ‰ ๋””์ž์ธ ๊ฐœ๋…์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋Œ€๋Ÿ‰์œผ๋กœ ์ฒ˜๋ฆฌ๋œ ํŠธ๋ฆฌ๊ฑฐ๋Š” ํŠธ๋ฆฌ๊ฑฐ ์ปจํ…์ŠคํŠธ์˜ ๋ชจ๋“  sObject์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์‹คํ–‰ํ•œ ์ž‘์—…์ด ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์‹œ์ž‘๋œ ๊ฒฝ์šฐ ํŠธ๋ฆฌ๊ฑฐ๋Š” ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ž‘์—…์˜ ์ถœ์ฒ˜๊ฐ€ ๋Œ€๋Ÿ‰ DML ๋˜๋Š” API์ธ ๊ฒฝ์šฐ ํŠธ๋ฆฌ๊ฑฐ๋Š” ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ ๋ ˆ์ฝ”๋“œ ์ง‘ํ•ฉ์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด API๋ฅผ ํ†ตํ•ด ๋งŽ์€ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒฝ์šฐ ํŠธ๋ฆฌ๊ฑฐ๋Š” ์ „์ฒด ๋ ˆ์ฝ”๋“œ ์ง‘ํ•ฉ์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ ์ ˆํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ๋ฒ•์€ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๋ชจ๋“  ์ƒํ™ฉ์—์„œ ์ž‘๋™ํ•˜๋„๋ก ๋ ˆ์ฝ”๋“œ ์ปฌ๋ ‰์…˜์—์„œ ์ž‘๋™ํ•œ๋‹ค๊ณ  ํ•ญ์ƒ ๊ฐ€์ •ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ํŠธ๋ฆฌ๊ฑฐ(MyTriggerNotBulk)๋Š” ์˜ค๋กœ์ง€ ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๋งŒ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ž‘๋™ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜์— ์—ฌ๋Ÿฌ ๋ ˆ์ฝ”๋“œ๊ฐ€ ์‚ฝ์ž…๋œ ๊ฒฝ์šฐ ์ „์ฒด ๋ ˆ์ฝ”๋“œ ์ง‘ํ•ฉ์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ๋Œ€๋Ÿ‰์œผ๋กœ ์ฒ˜๋ฆฌ๋œ ๋ฒ„์ „์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

trigger MyTriggerNotBulk on Account(before insert) {
    Account a = Trigger.new[0];
    a.Description = 'New description';
}

์ด ์˜ˆ์ œ(MyTriggerBulk)๋Š” MyTriggerNotBulk์˜ ์ˆ˜์ •๋œ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. for ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  sObject๋ฅผ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฃจํ”„๋Š” Trigger.new์— ํ•˜๋‚˜์˜ sObject ๋˜๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ sObject๊ฐ€ ํฌํ•œ๋œ ๊ฒฝ์šฐ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

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

๋Œ€๋Ÿ‰ SOQL ์ˆ˜ํ–‰ํ•˜๊ธฐ

SOQL ์ฟผ๋ฆฌ๋Š” ๊ฐ•๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ์ผ ์ฟผ๋ฆฌ์—์„œ ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์—ฌ๋Ÿฌ ์กฐ๊ฑด์˜ ์กฐํ•ฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. SOQL ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์ ์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋” ์ ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์ˆ˜๋ฅผ ์ค„์ผ ๊ฒฝ์šฐ ๋™๊ธฐ Apex์˜ ๊ฒฝ์šฐ 100๊ฐœ์˜ SOQL ์ฟผ๋ฆฌ ๋˜๋Š” ๋น„๋™๊ธฐ์‹ Apex์˜ ๊ฒฝ์šฐ 200๊ฐœ์ธ ์ฟผ๋ฆฌ ์ œํ•œ์— ๋„๋‹ฌํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ํŠธ๋ฆฌ๊ฑฐ(SoqlTriggerNotBulk)๋Š” ํ”ผํ•ด์•ผ ํ•  SOQL ์ฟผ๋ฆฌ ํŒจํ„ด์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์˜ˆ์ œ์—์„œ๋Š” for ๋ฃจํ”„ ๋‚ด์—์„œ SOQL ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด ๊ฐ ๊ณ„์ •์— ๋Œ€ํ•œ ๊ด€๋ จ ๊ธฐํšŒ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉฐ, ์ด ์ฟผ๋ฆฌ๋Š” Trigger.new์˜ ๊ฐ ๊ณ„์ • sObject์— ๋Œ€ํ•ด ํ•œ ๋ฒˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๊ณ„์ • ๋ชฉ๋ก์ด ๋งŽ์€ ๊ฒฝ์šฐ for ๋ฃจํ”„ ๋‚ด๋ถ€์˜ SOQL ์ฟผ๋ฆฌ๋กœ ์ธํ•ด SOQL ์ฟผ๋ฆฌ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ ๊ถŒ์žฅ๋˜๋Š” ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

trigger SoqlTriggerNotBulk on Account(after update) {
    for(Account a : Trigger.new) {
        // Get child records for each account
        // Inefficient SOQL query as it runs once for each account!
        Opportunity[] opps = [SELECT Id,Name,CloseDate
                             FROM Opportunity WHERE AccountId=:a.Id];
        // Do some other processing
    }
}

๋‹ค์Œ ์˜ˆ์ œ(SoqlTriggerBulk)๋Š” ์ด์ „ ์˜ˆ์ œ๋ฅผ ์ˆ˜์ •ํ•œ ๋ฒ„์ „์œผ๋กœ์„œ SOQL ์ฟผ๋ฆฌ ์‹คํ–‰์˜ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. SOQL ์ฟผ๋ฆฌ๋Š” ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ธฐ๋ณธ ๋ฃจํ”„ ์™ธ๋ถ€์—์„œ ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

  • SOQL ์ฟผ๋ฆฌ๋Š” ๊ณ„์ •์˜ ๋‚ด๋ถ€ ์ฟผ๋ฆฌ(SELECT Id FROM Opportunities)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ„์ •์— ๋Œ€ํ•œ ๊ด€๋ จ ๊ธฐํšŒ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • SOQL ์ฟผ๋ฆฌ๋Š” IN ์ ˆ์„ ์‚ฌ์šฉํ•˜๊ณ  WHERE ์ ˆ(WHERE Id IN :Trigger.new)์˜ Trigger.new ๋ณ€์ˆ˜๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ ํŠธ๋ฆฌ๊ฑฐ ์ปจํ…์ŠคํŠธ ๋ ˆ์ฝ”๋“œ์— ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ์ด WHERE ์กฐ๊ฑด์€ ์ด ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์‹คํ–‰ํ•œ ๋ ˆ์ฝ”๋“œ๋กœ๋งŒ ๊ณ„์ •์„ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.

์ฟผ๋ฆฌ์˜ ๋‘ ๋ถ€๋ถ„์„ ๊ฒฐํ•ฉํ•˜๋ฉด ๊ฐ ๊ณ„์ •์˜ ๊ด€๋ จ ๊ธฐํšŒ๊ฐ€ ์žˆ๋Š” ์ด ํŠธ๋ฆฌ๊ฑฐ์˜ ๊ณ„์ •์—์„œ ํ˜ธ์ถœ ํ•œ ๋ฒˆ์œผ๋กœ ์›ํ•˜๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ์™€ ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ for ๋ฃจํ”„๋Š” ์ปฌ๋ ‰์…˜ ๋ณ€์ˆ˜(์ด ๊ฒฝ์šฐ acctsWithOpps)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€์‹ฌ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค. ์ปฌ๋ ‰์…˜ ๋ณ€์ˆ˜๋Š” SOQL ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์œ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•  ๊ฒฝ์šฐ for ๋ฃจํ”„๋Š” ์ž‘์—…ํ•  ๋ ˆ์ฝ”๋“œ์— ๋Œ€ํ•ด์„œ๋งŒ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ด๋ฏธ ๊ฐ€์ ธ์™”์œผ๋ฏ€๋กœ ๋ฃจํ”„ ๋‚ด์—์„œ ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

trigger SoqlTriggerBulk on Account(after update) {
    // Perform SOQL query once.
    // Get the accounts and their related opportunities.
    List<Account> acctsWithOpps =
        [SELECT Id,(SELECT Id,Name,CloseDate FROM Opportunities)
         FROM Account WHERE Id IN :Trigger.new];
    // Iterate over the returned accounts
    for(Account a : acctsWithOpps) {
        Opportunity[] relatedOpps = a.Opportunities;
        // Do some other processing
    }
}

๋˜๋Š” ๊ณ„์ • ์ƒ์œ„ ๋ ˆ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ด ํŠธ๋ฆฌ๊ฑฐ ์ปจํ…์ŠคํŠธ ๋‚ด์—์„œ ๊ณ„์ •๊ณผ ๊ด€๋ จ๋œ ๊ธฐํšŒ๋งŒ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ชฉ๋ก์€ Trigger.new(WHERE AccountId IN :Trigger.new)์—์„œ ๊ณ„์ • ID์— ๋Œ€ํ•œ ๊ธฐํšŒ์˜ AccountId ํ•„๋“œ์™€ ์ผ์น˜ํ•จ์œผ๋กœ์จ WHERE ์ ˆ์—์„œ ์ง€์ •๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜๋œ ๊ธฐํšŒ๋Š” ํŠน์ • ๊ณ„์ •์ด ์•„๋‹ˆ๋ผ ์ด ํŠธ๋ฆฌ๊ฑฐ ์ปจํ…์ŠคํŠธ์˜ ๋ชจ๋“  ๊ณ„์ •์— ๋Œ€ํ•œ ๊ธฐํšŒ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ๋ชจ๋“  ๊ด€๋ จ ๊ธฐํšŒ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

trigger SoqlTriggerBulk on Account(after update) {
    // Perform SOQL query once.
    // Get the related opportunities for the accounts in this trigger.
    List<Opportunity> relatedOpps = [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.new];
    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {
        // Do some other processing
    }
}

ํ•˜๋‚˜์˜ ๋ช…๋ น๋ฌธ์ธ SOQL for ๋ฃจํ”„์—์„œ SOQL ์ฟผ๋ฆฌ๋ฅผ for ๋ฃจํ”„์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ์ด์ „ ์˜ˆ์ œ์˜ ํฌ๊ธฐ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ SOQL for ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด ๋Œ€๋Ÿ‰ ํŠธ๋ฆฌ๊ฑฐ์˜ ๋‹ค๋ฅธ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค.

trigger SoqlTriggerBulk on Account(after update) {
    // Perform SOQL query once.
    // Get the related opportunities for the accounts in this trigger,
    // and iterate over those records.
    for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.new]) {
        // Do some other processing
    }
}
Note

ํŠธ๋ฆฌ๊ฑฐ๋Š” ํ•œ ๋ฒˆ์— 200๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ ๋ฐฐ์น˜์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ 400๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ๋กœ ์ธํ•ด ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ์‹คํ–‰๋  ๊ฒฝ์šฐ ํŠธ๋ฆฌ๊ฑฐ๋Š” 200๊ฐœ ๋ ˆ์ฝ”๋“œ๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ๋‘ ๋ฒˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ด์œ ๋กœ ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋ ˆ์ฝ”๋“œ๋„ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ํŠธ๋ฆฌ๊ฑฐ์—์„œ ๋ ˆ์ฝ”๋“œ ์ผ๊ด„ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ SOQL for ๋ฃจํ”„์˜ ์ด์ ์„ ์–ป์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” SOQL for ๋ฃจํ”„๊ฐ€ ๋‘ ๋ฒˆ ํ˜ธ์ถœ๋˜์ง€๋งŒ ๋…๋ฆฝ ์‹คํ–‰ํ˜• SOQL ์ฟผ๋ฆฌ๋„ ๋‘ ๋ฒˆ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ SOQL for ๋ฃจํ”„๋Š” ์ปฌ๋ ‰์…˜ ๋ณ€์ˆ˜๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์—ฌ์ „ํžˆ ํŽธ๋ฆฌํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

๋Œ€๋Ÿ‰ DML ์ˆ˜ํ–‰ํ•˜๊ธฐ

ํŠธ๋ฆฌ๊ฑฐ ๋˜๋Š” ํด๋ž˜์Šค์—์„œ DML ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ sObject ์ปฌ๋ ‰์…˜์— ๋Œ€ํ•ด DML ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ sObject์—์„œ DML์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋ฆฌ์†Œ์Šค๊ฐ€ ๋น„ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. Apex ๋Ÿฐํƒ€์ž„์€ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์—์„œ ์ตœ๋Œ€ 150๊ฐœ์˜ DML ํ˜ธ์ถœ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด ํŠธ๋ฆฌ๊ฑฐ(DmlTriggerNotBulk)๋Š” ๊ด€๋ จ ๊ธฐํšŒ๋“ค์— ๋Œ€ํ•˜์—ฌ ๋ฐ˜๋ณต๋˜๋Š” for ๋ฃจํ”„ ๋‚ด์—์„œ ์—…๋ฐ์ดํŠธ ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ์กฐ๊ฑด์ด ์ถฉ์กฑ๋  ๊ฒฝ์šฐ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๊ธฐํšŒ ์„ค๋ช…์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ update ๋ฌธ์€ ๊ฐ ๊ธฐํšŒ์— ๋Œ€ํ•ด ํ•œ ๋ฒˆ์”ฉ ๋น„ํšจ์œจ์ ์œผ๋กœ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ๋Œ€๋Ÿ‰ ๊ณ„์ • ์—…๋ฐ์ดํŠธ ์ž‘์—…์ด ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์‹คํ–‰ํ•œ ๊ฒฝ์šฐ ๋งŽ์€ ๊ณ„์ •์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๊ณ„์ •์— ํ•˜๋‚˜ ๋˜๋Š” ๋‘ ๊ฐœ์˜ ๊ธฐํšŒ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ 150๊ฐœ ์ด์ƒ์˜ ๊ธฐํšŒ๋กœ ์‰ฝ๊ฒŒ ๋๋‚  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. DML ๋ฌธ ์ œํ•œ์€ 150๊ฐœ ํ˜ธ์ถœ์ž…๋‹ˆ๋‹ค.

trigger DmlTriggerNotBulk on Account(after update) {
    // Get the related opportunities for the accounts in this trigger.
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.new];
    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {
        // Update the description when probability is greater
        // than 50% but less than 100%
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            // Update once for each opportunity -- not efficient!
            update opp;
        }
    }
}

๋‹ค์Œ ์˜ˆ์ œ(DmlTriggerBulk)๋Š” ๊ธฐํšŒ ๋ชฉ๋ก์—์„œ ๋‹จ ํ•˜๋‚˜์˜ DML ํ˜ธ์ถœ๋กœ DML์„ ํšจ์œจ์ ์œผ๋กœ ๋Œ€๋Ÿ‰์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” ๋ฃจํ”„์—์„œ ๊ธฐํšŒ(oppsToUpdate) ๋ชฉ๋ก์— ์—…๋ฐ์ดํŠธํ•  Opportunity sObject๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋ชจ๋“  ๊ธฐํšŒ๊ฐ€ ๋ชฉ๋ก์— ์ถ”๊ฐ€๋œ ํ›„ ์ด ๋ชฉ๋ก์˜ ๋ฃจํ”„ ์™ธ๋ถ€์—์„œ DML ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์€ ์—…๋ฐ์ดํŠธ๋˜๋Š” sObject์˜ ๊ฐœ์ˆ˜์— ๊ด€๊ณ„์—†์ด ํ•˜๋‚˜์˜ DML ํ˜ธ์ถœ๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

trigger DmlTriggerBulk on Account(after update) {
    // Get the related opportunities for the accounts in this trigger.
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.new];
    List<Opportunity> oppsToUpdate = new List<Opportunity>();
    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {
        // Update the description when probability is greater
        // than 50% but less than 100%
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            oppsToUpdate.add(opp);
        }
    }
    // Perform DML on a collection
    update oppsToUpdate;
}

๊ณ„์ • ๊ด€๋ จ ๊ธฐํšŒ์— ์•ก์„ธ์Šคํ•˜๋Š” ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ํ•™์Šตํ•œ ๋””์ž์ธ ํŒจํ„ด์„ ์ ์šฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด์ „ ์œ ๋‹›์—์„œ AddRelatedRecord ํŠธ๋ฆฌ๊ฑฐ์— ๋Œ€ํ•œ ํŠธ๋ฆฌ๊ฑฐ ์˜ˆ์ œ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. AddRelatedRecord ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋Œ€๋Ÿ‰์œผ๋กœ ์ž‘๋™ํ•˜์ง€๋งŒ ์ด์ „ ์œ ๋‹›์˜ ํŠธ๋ฆฌ๊ฑฐ๋Š” Trigger.New sObject ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ชจ๋‘ ๋ฐ˜๋ณตํ•˜๋ฏ€๋กœ ๊ฐ€๋Šฅํ•œ ํ•œ ํšจ์œจ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ๊ด€์‹ฌ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ๋งŒ ๊ฐ€์ ธ์˜จ ๋‹ค์Œ ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณตํ•˜๋„๋ก ํŠธ๋ฆฌ๊ฑฐ ์ฝ”๋“œ์™€ SOQL ์ฟผ๋ฆฌ๋ฅผ ๋ชจ๋‘ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ๋ผ๋„ ๊ฑฑ์ •ํ•˜์ง€ ์•Š์œผ์…”๋„ ๋ฉ๋‹ˆ๋‹ค. ์ด ์„น์…˜์—์„œ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

AddRelatedRecord ํŠธ๋ฆฌ๊ฑฐ์— ๋Œ€ํ•œ ์š”๊ตฌ ์‚ฌํ•ญ์„ ๊ฒ€ํ† ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํŠธ๋ฆฌ๊ฑฐ๋Š” ๊ณ„์ •์ด ์‚ฝ์ž…๋˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ๋œ ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ํŠธ๋ฆฌ๊ฑฐ๋Š” ์•„์ง ๊ธฐํšŒ๊ฐ€ ์—†๋Š” ๋ชจ๋“  ๊ณ„์ •์— ๋Œ€ํ•ด ๊ธฐ๋ณธ ๊ธฐํšŒ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € ์ƒˆ๋กœ ์‚ฝ์ž…๋œ ๊ณ„์ •์—๋Š” ๊ธฐ๋ณธ ๊ธฐํšŒ๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๊ณ„์ •์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—…๋ฐ์ดํŠธ๋œ ๊ณ„์ •์˜ ๊ฒฝ์šฐ ๊ด€๋ จ๋œ ๊ธฐํšŒ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ Trigger.operationType ์ปจํ…์ŠคํŠธ ๋ณ€์ˆ˜์— switch ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฝ์ž…๊ณผ ์—…๋ฐ์ดํŠธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ตฌ๋ถ„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ toProcess ๋ณ€์ˆ˜๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๊ณ„์ •์„ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์˜ ์˜ˆ๋ฅผ ํ™•์ธํ•ด ๋ณด์„ธ์š”.

List<Account> toProcess = null;
switch on Trigger.operationType {
    when AFTER_INSERT {
        // do stuff
    }
    when AFTER_UPDATE {
        // do stuff
    }
}

๋ชจ๋“  ๊ณ„์ • ์‚ฝ์ž…์˜ ๊ฒฝ์šฐ ์ƒˆ ๊ณ„์ •์„ toProcess ๋ชฉ๋ก์— ํ• ๋‹นํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

when AFTER_INSERT {
ย ย ย ย ย toProcess = Trigger.New;
}

์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด ์ด ํŠธ๋ฆฌ๊ฑฐ์˜ ๊ธฐ์กด ๊ณ„์ •์— ๊ด€๋ จ ๊ธฐํšŒ๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŠธ๋ฆฌ๊ฑฐ๋Š” after ํŠธ๋ฆฌ๊ฑฐ์ด๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฟผ๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ toProcess ๋ชฉ๋ก์— ํ• ๋‹นํ•˜๋Š” SOQL ๋ฌธ์ž…๋‹ˆ๋‹ค.

when AFTER_UPDATE {
ย ย ย ย ย toProcess = [SELECT Id,Name FROM Account
ย ย ย ย ย ย ย ย ย        ย ย WHERE Id IN :Trigger.New AND
ย ย ย ย ย ย ย ย ย ย ย        Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId in :Trigger.New)];
}

์ด์ œ for ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ toProcess ๊ณ„์ • ๋ชฉ๋ก์„ ๋ฐ˜๋ณตํ•˜๊ณ  oppList์— ๊ด€๋ จ ๊ธฐ๋ณธ ๊ธฐํšŒ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์™„๋ฃŒ๋˜๋ฉด insert DML ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐํšŒ ๋ชฉ๋ก์„ ์ผ๊ด„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ์ด๋ฏธ ์ด์ „ ์œ ๋‹›์˜ AddRelatedRecord ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ƒ์„ฑํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ๋‚ด์šฉ์„ ๋‹ค์Œ ํŠธ๋ฆฌ๊ฑฐ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ํ•ด๋‹น ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์„ ๊ฒฝ์šฐ Developer Console์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ํŠธ๋ฆฌ๊ฑฐ ์ด๋ฆ„์— AddRelatedRecord๋ฅผ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.
trigger AddRelatedRecord on Account(after insert, after update) {
    List<Opportunity> oppList = new List<Opportunity>();
    // Add an opportunity for each account if it doesn't already have one.
    // Iterate over accounts that are in this trigger but that don't have opportunities.
    List<Account> toProcess = null;
    switch on Trigger.operationType {
        when AFTER_INSERT {
        // All inserted Accounts will need the Opportunity, so there is no need to perform the query
            toProcess = Trigger.New;
        }
        when AFTER_UPDATE {
            toProcess = [SELECT Id,Name FROM Account
                         WHERE Id IN :Trigger.New AND
                         Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId in :Trigger.New)];
        }
    }
    for (Account a : toProcess) {
        // Add a default opportunity for this account
        oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                    StageName='Prospecting',
                                    CloseDate=System.today().addMonths(1),
                                    AccountId=a.Id));
    }
    if (oppList.size() > 0) {
        insert oppList;
    }
}
  1. ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด Salesforce ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ๊ณ„์ •์„ ๋งŒ๋“ค๊ณ  Lions and Cats(์‚ฌ์ž ๋ฐ ๊ณ ์–‘์ด)๋กœ ์ด๋ฆ„์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  2. ๊ณ„์ • ํŽ˜์ด์ง€์˜ ๊ธฐํšŒ ๊ด€๋ จ ๋ชฉ๋ก์—์„œ ์ƒˆ ๊ธฐํšŒ Lions and Cats(์‚ฌ์ž ๋ฐ ๊ณ ์–‘์ด)๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค. ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๊ธฐํšŒ๋ฅผ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฆฌ์†Œ์Šค

Salesforce ๋„์›€๋ง์—์„œ Trailhead ํ”ผ๋“œ๋ฐฑ์„ ๊ณต์œ ํ•˜์„ธ์š”.

Trailhead์— ๊ด€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์˜๊ฒฌ์— ๊ท€ ๊ธฐ์šธ์ด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด์ œ Salesforce ๋„์›€๋ง ์‚ฌ์ดํŠธ์—์„œ ์–ธ์ œ๋“ ์ง€ ์ƒˆ๋กœ์šด ํ”ผ๋“œ๋ฐฑ ์–‘์‹์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ ์˜๊ฒฌ ๊ณต์œ ํ•˜๊ธฐ