[{"data":1,"prerenderedAt":1191},["ShallowReactive",2],{"post-document:\u002Fjava\u002Fspring\u002F2026\u002F08\u002F22\u002Fspring-async\u002F":3},{"id":4,"title":5,"body":6,"categories":1171,"date":1173,"description":1174,"extension":1175,"image":1176,"key_concepts":1176,"last_modified_at":1176,"legacyPath":1177,"meta":1178,"navigation":300,"part":1180,"path":1181,"published":300,"robots":1176,"seo":1182,"series":1183,"stem":1184,"strengths":1176,"summary":1176,"tags":1185,"tradeoffs":1176,"__hash__":1190},"posts\u002Fposts\u002FBackend\u002FSPRING\u002FSpring\u002F2026-08-22-spring-async.md","Spring 기초 Part 10: 비동기 처리와 @Async",{"type":7,"value":8,"toc":1153},"minimark",[9,18,25,28,38,44,47,52,165,168,170,177,183,216,261,267,273,275,281,336,339,423,429,485,487,491,497,526,529,558,568,577,579,583,586,669,693,747,750,752,756,761,764,821,824,830,832,836,843,846,876,882,891,930,933,935,941,944,973,979,982,989,991,995,1041,1046,1048,1052,1125,1132,1136,1149],[10,11,13,14],"h2",{"id":12},"spring-기초-part-10-비동기-처리와-async","Spring 기초 Part 10: 비동기 처리와 ",[15,16,17],"code",{},"@Async",[19,20,21],"blockquote",{},[22,23,24],"p",{},"비동기 처리는 작업의 완료를 기다리는 대신 다른 실행 흐름에 작업을 맡기고, 호출한 쪽은 다음 작업을 진행하는 방식이다.",[22,26,27],{},"이메일 발송에 5초가 걸린다고 해보자.",[29,30,36],"pre",{"className":31,"code":33,"language":34,"meta":35},[32],"language-text","동기 처리\n\n회원 저장 0.1초\n   ↓\n이메일 전송 5초\n   ↓\n응답\n","text","",[15,37,33],{"__ignoreMap":35},[29,39,42],{"className":40,"code":41,"language":34,"meta":35},[32],"비동기 처리\n\n회원 저장\n   ↓\n이메일 작업 제출 ─────→ 다른 Thread에서 전송\n   ↓\n바로 응답\n",[15,43,41],{"__ignoreMap":35},[45,46],"hr",{},[48,49,51],"h3",{"id":50},"_1-먼저-알아둘-단어","1. 먼저 알아둘 단어",[53,54,55,71],"table",{},[56,57,58],"thead",{},[59,60,61,65,68],"tr",{},[62,63,64],"th",{},"용어",[62,66,67],{},"정의",[62,69,70],{},"쉽게 말하면",[72,73,74,86,97,108,119,130,141,152],"tbody",{},[59,75,76,80,83],{},[77,78,79],"td",{},"동기(Synchronous)",[77,81,82],{},"이전 작업이 끝날 때까지 기다린 뒤 다음 작업 실행",[77,84,85],{},"순서대로 하나씩 처리",[59,87,88,91,94],{},[77,89,90],{},"비동기(Asynchronous)",[77,92,93],{},"작업을 맡긴 뒤 완료를 기다리지 않고 다음 작업 진행",[77,95,96],{},"다른 실행 흐름에 맡기기",[59,98,99,102,105],{},[77,100,101],{},"Thread",[77,103,104],{},"코드를 실행하는 하나의 흐름",[77,106,107],{},"실제 작업자",[59,109,110,113,116],{},[77,111,112],{},"Thread Pool",[77,114,115],{},"미리 준비한 여러 Thread를 재사용하는 구조",[77,117,118],{},"작업자 대기실",[59,120,121,124,127],{},[77,122,123],{},"Task",[77,125,126],{},"Executor에 맡길 실행 단위",[77,128,129],{},"이메일 한 건 발송",[59,131,132,135,138],{},[77,133,134],{},"Executor",[77,136,137],{},"Task를 어떤 Thread에서 실행할지 관리하는 객체",[77,139,140],{},"작업 배정 관리자",[59,142,143,146,149],{},[77,144,145],{},"Future",[77,147,148],{},"나중에 완료될 결과를 표현하는 객체",[77,150,151],{},"결과 수령표",[59,153,154,159,162],{},[77,155,156],{},[15,157,158],{},"CompletableFuture",[77,160,161],{},"비동기 결과를 연결하고 조합할 수 있는 Future",[77,163,164],{},"완료 후 다음 작업 연결",[22,166,167],{},"비동기라고 해서 작업 자체의 실행 시간이 무조건 줄어드는 것은 아니다. 호출자가 기다리지 않고 다른 일을 할 수 있도록 실행 흐름을 분리하는 것이 핵심이다.",[45,169],{},[48,171,173,174,176],{"id":172},"_2-async-활성화","2. ",[15,175,17],{}," 활성화",[22,178,179,180,182],{},"Spring에서 ",[15,181,17],{},"를 사용하려면 비동기 실행 기능을 활성화한다.",[29,184,188],{"className":185,"code":186,"language":187,"meta":35,"style":35},"language-java shiki shiki-themes github-light github-dark","@Configuration\n@EnableAsync\npublic class AsyncConfig {\n}\n","java",[15,189,190,198,204,210],{"__ignoreMap":35},[191,192,195],"span",{"class":193,"line":194},"line",1,[191,196,197],{},"@Configuration\n",[191,199,201],{"class":193,"line":200},2,[191,202,203],{},"@EnableAsync\n",[191,205,207],{"class":193,"line":206},3,[191,208,209],{},"public class AsyncConfig {\n",[191,211,213],{"class":193,"line":212},4,[191,214,215],{},"}\n",[53,217,218,228],{},[56,219,220],{},[59,221,222,225],{},[62,223,224],{},"어노테이션",[62,226,227],{},"역할",[72,229,230,240,252],{},[59,231,232,237],{},[77,233,234],{},[15,235,236],{},"@Configuration",[77,238,239],{},"설정 클래스를 Spring Bean으로 등록",[59,241,242,247],{},[77,243,244],{},[15,245,246],{},"@EnableAsync",[77,248,249,251],{},[15,250,17],{}," 어노테이션 처리 활성화",[59,253,254,258],{},[77,255,256],{},[15,257,17],{},[77,259,260],{},"해당 메서드 호출을 TaskExecutor에 제출",[22,262,263,264,266],{},"Spring의 기본 ",[15,265,17],{}," 처리 방식은 Proxy 기반이다.",[29,268,271],{"className":269,"code":270,"language":34,"meta":35},[32],"호출자\n  → Async Proxy\n  → TaskExecutor에 작업 제출\n  → 호출자는 다음 작업 진행\n\nTaskExecutor\n  → 다른 Thread에서 실제 메서드 실행\n",[15,272,270],{"__ignoreMap":35},[45,274],{},[48,276,278,279],{"id":277},"_3-가장-간단한-async","3. 가장 간단한 ",[15,280,17],{},[29,282,284],{"className":185,"code":283,"language":187,"meta":35,"style":35},"@Service\npublic class EmailService {\n\n    @Async\n    public void sendWelcomeEmail(String email) {\n        System.out.println(Thread.currentThread().getName());\n        \u002F\u002F 이메일 전송\n    }\n}\n",[15,285,286,291,296,302,307,313,319,325,331],{"__ignoreMap":35},[191,287,288],{"class":193,"line":194},[191,289,290],{},"@Service\n",[191,292,293],{"class":193,"line":200},[191,294,295],{},"public class EmailService {\n",[191,297,298],{"class":193,"line":206},[191,299,301],{"emptyLinePlaceholder":300},true,"\n",[191,303,304],{"class":193,"line":212},[191,305,306],{},"    @Async\n",[191,308,310],{"class":193,"line":309},5,[191,311,312],{},"    public void sendWelcomeEmail(String email) {\n",[191,314,316],{"class":193,"line":315},6,[191,317,318],{},"        System.out.println(Thread.currentThread().getName());\n",[191,320,322],{"class":193,"line":321},7,[191,323,324],{},"        \u002F\u002F 이메일 전송\n",[191,326,328],{"class":193,"line":327},8,[191,329,330],{},"    }\n",[191,332,334],{"class":193,"line":333},9,[191,335,215],{},[22,337,338],{},"다른 Bean에서 호출한다.",[29,340,342],{"className":185,"code":341,"language":187,"meta":35,"style":35},"@Service\npublic class UserService {\n\n    private final EmailService emailService;\n\n    public UserService(EmailService emailService) {\n        this.emailService = emailService;\n    }\n\n    public void register(String email) {\n        \u002F\u002F 회원 저장\n        emailService.sendWelcomeEmail(email);\n\n        \u002F\u002F 이메일 완료를 기다리지 않고 다음 코드 실행\n    }\n}\n",[15,343,344,348,353,357,362,366,371,376,380,384,390,396,402,407,413,418],{"__ignoreMap":35},[191,345,346],{"class":193,"line":194},[191,347,290],{},[191,349,350],{"class":193,"line":200},[191,351,352],{},"public class UserService {\n",[191,354,355],{"class":193,"line":206},[191,356,301],{"emptyLinePlaceholder":300},[191,358,359],{"class":193,"line":212},[191,360,361],{},"    private final EmailService emailService;\n",[191,363,364],{"class":193,"line":309},[191,365,301],{"emptyLinePlaceholder":300},[191,367,368],{"class":193,"line":315},[191,369,370],{},"    public UserService(EmailService emailService) {\n",[191,372,373],{"class":193,"line":321},[191,374,375],{},"        this.emailService = emailService;\n",[191,377,378],{"class":193,"line":327},[191,379,330],{},[191,381,382],{"class":193,"line":333},[191,383,301],{"emptyLinePlaceholder":300},[191,385,387],{"class":193,"line":386},10,[191,388,389],{},"    public void register(String email) {\n",[191,391,393],{"class":193,"line":392},11,[191,394,395],{},"        \u002F\u002F 회원 저장\n",[191,397,399],{"class":193,"line":398},12,[191,400,401],{},"        emailService.sendWelcomeEmail(email);\n",[191,403,405],{"class":193,"line":404},13,[191,406,301],{"emptyLinePlaceholder":300},[191,408,410],{"class":193,"line":409},14,[191,411,412],{},"        \u002F\u002F 이메일 완료를 기다리지 않고 다음 코드 실행\n",[191,414,416],{"class":193,"line":415},15,[191,417,330],{},[191,419,421],{"class":193,"line":420},16,[191,422,215],{},[22,424,425],{},[426,427,428],"strong",{},"@Async 호출 흐름",[29,430,434],{"className":431,"code":432,"language":433,"meta":35,"style":35},"language-mermaid shiki shiki-themes github-light github-dark","sequenceDiagram\n    participant U as UserService\n    participant P as Async Proxy\n    participant E as TaskExecutor\n    participant T as EmailService\n\n    U->>P: sendWelcomeEmail 호출\n    P->>E: Task 제출\n    P-->>U: 즉시 반환\n    E->>T: 별도 Thread에서 메서드 실행\n","mermaid",[15,435,436,441,446,451,456,461,465,470,475,480],{"__ignoreMap":35},[191,437,438],{"class":193,"line":194},[191,439,440],{},"sequenceDiagram\n",[191,442,443],{"class":193,"line":200},[191,444,445],{},"    participant U as UserService\n",[191,447,448],{"class":193,"line":206},[191,449,450],{},"    participant P as Async Proxy\n",[191,452,453],{"class":193,"line":212},[191,454,455],{},"    participant E as TaskExecutor\n",[191,457,458],{"class":193,"line":309},[191,459,460],{},"    participant T as EmailService\n",[191,462,463],{"class":193,"line":315},[191,464,301],{"emptyLinePlaceholder":300},[191,466,467],{"class":193,"line":321},[191,468,469],{},"    U->>P: sendWelcomeEmail 호출\n",[191,471,472],{"class":193,"line":327},[191,473,474],{},"    P->>E: Task 제출\n",[191,476,477],{"class":193,"line":333},[191,478,479],{},"    P-->>U: 즉시 반환\n",[191,481,482],{"class":193,"line":386},[191,483,484],{},"    E->>T: 별도 Thread에서 메서드 실행\n",[45,486],{},[48,488,490],{"id":489},"_4-결과가-필요한-경우","4. 결과가 필요한 경우",[22,492,493,494,496],{},"결과가 필요하다면 ",[15,495,158],{},"를 반환할 수 있다.",[29,498,500],{"className":185,"code":499,"language":187,"meta":35,"style":35},"@Async\npublic CompletableFuture\u003CString> createReport(Long userId) {\n    String result = reportGenerator.generate(userId);\n    return CompletableFuture.completedFuture(result);\n}\n",[15,501,502,507,512,517,522],{"__ignoreMap":35},[191,503,504],{"class":193,"line":194},[191,505,506],{},"@Async\n",[191,508,509],{"class":193,"line":200},[191,510,511],{},"public CompletableFuture\u003CString> createReport(Long userId) {\n",[191,513,514],{"class":193,"line":206},[191,515,516],{},"    String result = reportGenerator.generate(userId);\n",[191,518,519],{"class":193,"line":212},[191,520,521],{},"    return CompletableFuture.completedFuture(result);\n",[191,523,524],{"class":193,"line":309},[191,525,215],{},[22,527,528],{},"호출한 쪽은 완료 후 실행할 작업을 연결할 수 있다.",[29,530,532],{"className":185,"code":531,"language":187,"meta":35,"style":35},"CompletableFuture\u003CString> future = reportService.createReport(userId);\n\nfuture.thenAccept(result -> {\n    System.out.println(\"보고서 생성 완료: \" + result);\n});\n",[15,533,534,539,543,548,553],{"__ignoreMap":35},[191,535,536],{"class":193,"line":194},[191,537,538],{},"CompletableFuture\u003CString> future = reportService.createReport(userId);\n",[191,540,541],{"class":193,"line":200},[191,542,301],{"emptyLinePlaceholder":300},[191,544,545],{"class":193,"line":206},[191,546,547],{},"future.thenAccept(result -> {\n",[191,549,550],{"class":193,"line":212},[191,551,552],{},"    System.out.println(\"보고서 생성 완료: \" + result);\n",[191,554,555],{"class":193,"line":309},[191,556,557],{},"});\n",[22,559,560,563,564,567],{},[15,561,562],{},"join()","이나 ",[15,565,566],{},"get()","을 바로 호출하면 결과가 나올 때까지 현재 Thread가 기다리므로 비동기 효과가 줄어들 수 있다.",[29,569,571],{"className":185,"code":570,"language":187,"meta":35,"style":35},"String result = future.join(); \u002F\u002F 이 지점에서 완료까지 대기\n",[15,572,573],{"__ignoreMap":35},[191,574,575],{"class":193,"line":194},[191,576,570],{},[45,578],{},[48,580,582],{"id":581},"_5-thread-pool-설정","5. Thread Pool 설정",[22,584,585],{},"운영 환경에서는 작업량에 맞는 Executor를 명시적으로 설정하는 것이 좋다.",[29,587,589],{"className":185,"code":588,"language":187,"meta":35,"style":35},"@Configuration\n@EnableAsync\npublic class AsyncConfig {\n\n    @Bean(name = \"emailExecutor\")\n    public Executor emailExecutor() {\n        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n\n        executor.setCorePoolSize(2);\n        executor.setMaxPoolSize(5);\n        executor.setQueueCapacity(100);\n        executor.setThreadNamePrefix(\"email-\");\n        executor.initialize();\n\n        return executor;\n    }\n}\n",[15,590,591,595,599,603,607,612,617,622,626,631,636,641,646,651,655,660,664],{"__ignoreMap":35},[191,592,593],{"class":193,"line":194},[191,594,197],{},[191,596,597],{"class":193,"line":200},[191,598,203],{},[191,600,601],{"class":193,"line":206},[191,602,209],{},[191,604,605],{"class":193,"line":212},[191,606,301],{"emptyLinePlaceholder":300},[191,608,609],{"class":193,"line":309},[191,610,611],{},"    @Bean(name = \"emailExecutor\")\n",[191,613,614],{"class":193,"line":315},[191,615,616],{},"    public Executor emailExecutor() {\n",[191,618,619],{"class":193,"line":321},[191,620,621],{},"        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\n",[191,623,624],{"class":193,"line":327},[191,625,301],{"emptyLinePlaceholder":300},[191,627,628],{"class":193,"line":333},[191,629,630],{},"        executor.setCorePoolSize(2);\n",[191,632,633],{"class":193,"line":386},[191,634,635],{},"        executor.setMaxPoolSize(5);\n",[191,637,638],{"class":193,"line":392},[191,639,640],{},"        executor.setQueueCapacity(100);\n",[191,642,643],{"class":193,"line":398},[191,644,645],{},"        executor.setThreadNamePrefix(\"email-\");\n",[191,647,648],{"class":193,"line":404},[191,649,650],{},"        executor.initialize();\n",[191,652,653],{"class":193,"line":409},[191,654,301],{"emptyLinePlaceholder":300},[191,656,657],{"class":193,"line":415},[191,658,659],{},"        return executor;\n",[191,661,662],{"class":193,"line":420},[191,663,330],{},[191,665,667],{"class":193,"line":666},17,[191,668,215],{},[29,670,672],{"className":185,"code":671,"language":187,"meta":35,"style":35},"@Async(\"emailExecutor\")\npublic void sendWelcomeEmail(String email) {\n    \u002F\u002F emailExecutor에서 실행\n}\n",[15,673,674,679,684,689],{"__ignoreMap":35},[191,675,676],{"class":193,"line":194},[191,677,678],{},"@Async(\"emailExecutor\")\n",[191,680,681],{"class":193,"line":200},[191,682,683],{},"public void sendWelcomeEmail(String email) {\n",[191,685,686],{"class":193,"line":206},[191,687,688],{},"    \u002F\u002F emailExecutor에서 실행\n",[191,690,691],{"class":193,"line":212},[191,692,215],{},[53,694,695,705],{},[56,696,697],{},[59,698,699,702],{},[62,700,701],{},"설정",[62,703,704],{},"의미",[72,706,707,717,727,737],{},[59,708,709,714],{},[77,710,711],{},[15,712,713],{},"corePoolSize",[77,715,716],{},"기본적으로 유지할 Thread 수",[59,718,719,724],{},[77,720,721],{},[15,722,723],{},"maxPoolSize",[77,725,726],{},"최대로 늘어날 수 있는 Thread 수",[59,728,729,734],{},[77,730,731],{},[15,732,733],{},"queueCapacity",[77,735,736],{},"실행을 기다리는 Task의 Queue 크기",[59,738,739,744],{},[77,740,741],{},[15,742,743],{},"threadNamePrefix",[77,745,746],{},"로그에서 Thread를 구분할 이름",[22,748,749],{},"Thread를 많이 만들면 무조건 빨라지는 것이 아니다. CPU, 메모리, 외부 시스템 처리량을 고려해 설정해야 한다.",[45,751],{},[48,753,755],{"id":754},"_6-자기-호출-문제","6. 자기 호출 문제",[22,757,758,760],{},[15,759,17],{},"도 기본적으로 Proxy를 통해 동작한다.",[22,762,763],{},"같은 객체 안에서 비동기 메서드를 직접 호출하면 Proxy를 거치지 않아 현재 Thread에서 실행될 수 있다.",[29,765,767],{"className":185,"code":766,"language":187,"meta":35,"style":35},"@Service\npublic class EmailService {\n\n    public void registerAndSend() {\n        sendEmail(); \u002F\u002F 자기 호출\n    }\n\n    @Async\n    public void sendEmail() {\n        \u002F\u002F 비동기로 실행되지 않을 수 있음\n    }\n}\n",[15,768,769,773,777,781,786,791,795,799,803,808,813,817],{"__ignoreMap":35},[191,770,771],{"class":193,"line":194},[191,772,290],{},[191,774,775],{"class":193,"line":200},[191,776,295],{},[191,778,779],{"class":193,"line":206},[191,780,301],{"emptyLinePlaceholder":300},[191,782,783],{"class":193,"line":212},[191,784,785],{},"    public void registerAndSend() {\n",[191,787,788],{"class":193,"line":309},[191,789,790],{},"        sendEmail(); \u002F\u002F 자기 호출\n",[191,792,793],{"class":193,"line":315},[191,794,330],{},[191,796,797],{"class":193,"line":321},[191,798,301],{"emptyLinePlaceholder":300},[191,800,801],{"class":193,"line":327},[191,802,306],{},[191,804,805],{"class":193,"line":333},[191,806,807],{},"    public void sendEmail() {\n",[191,809,810],{"class":193,"line":386},[191,811,812],{},"        \u002F\u002F 비동기로 실행되지 않을 수 있음\n",[191,814,815],{"class":193,"line":392},[191,816,330],{},[191,818,819],{"class":193,"line":398},[191,820,215],{},[22,822,823],{},"비동기 메서드를 별도 Bean으로 분리하고 외부에서 호출한다.",[29,825,828],{"className":826,"code":827,"language":34,"meta":35},[32],"UserService\n  → EmailService Proxy\n  → TaskExecutor\n  → EmailService.sendEmail()\n",[15,829,827],{"__ignoreMap":35},[45,831],{},[48,833,835],{"id":834},"_7-예외-처리","7. 예외 처리",[837,838,840,842],"h4",{"id":839},"completablefuture-반환",[15,841,158],{}," 반환",[22,844,845],{},"비동기 결과를 통해 예외를 처리할 수 있다.",[29,847,849],{"className":185,"code":848,"language":187,"meta":35,"style":35},"reportService.createReport(userId)\n        .exceptionally(exception -> {\n            System.out.println(exception.getMessage());\n            return \"보고서 생성 실패\";\n        });\n",[15,850,851,856,861,866,871],{"__ignoreMap":35},[191,852,853],{"class":193,"line":194},[191,854,855],{},"reportService.createReport(userId)\n",[191,857,858],{"class":193,"line":200},[191,859,860],{},"        .exceptionally(exception -> {\n",[191,862,863],{"class":193,"line":206},[191,864,865],{},"            System.out.println(exception.getMessage());\n",[191,867,868],{"class":193,"line":212},[191,869,870],{},"            return \"보고서 생성 실패\";\n",[191,872,873],{"class":193,"line":309},[191,874,875],{},"        });\n",[837,877,879,842],{"id":878},"void-반환",[15,880,881],{},"void",[22,883,884,886,887,890],{},[15,885,881],{}," 메서드에서 발생한 예외는 호출자에게 직접 전달할 수 없다. 기본적으로 로그에 남을 수 있으며, 필요하면 ",[15,888,889],{},"AsyncUncaughtExceptionHandler","를 설정한다.",[29,892,894],{"className":185,"code":893,"language":187,"meta":35,"style":35},"@Override\npublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {\n    return (exception, method, params) -> {\n        System.out.println(method.getName());\n        System.out.println(exception.getMessage());\n    };\n}\n",[15,895,896,901,906,911,916,921,926],{"__ignoreMap":35},[191,897,898],{"class":193,"line":194},[191,899,900],{},"@Override\n",[191,902,903],{"class":193,"line":200},[191,904,905],{},"public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {\n",[191,907,908],{"class":193,"line":206},[191,909,910],{},"    return (exception, method, params) -> {\n",[191,912,913],{"class":193,"line":212},[191,914,915],{},"        System.out.println(method.getName());\n",[191,917,918],{"class":193,"line":309},[191,919,920],{},"        System.out.println(exception.getMessage());\n",[191,922,923],{"class":193,"line":315},[191,924,925],{},"    };\n",[191,927,928],{"class":193,"line":321},[191,929,215],{},[22,931,932],{},"중요한 비동기 작업은 실패 여부를 저장하고 재시도하거나 메시지 Queue를 사용하는 방식도 고려해야 한다.",[45,934],{},[48,936,938,939],{"id":937},"_8-트랜잭션과-async","8. 트랜잭션과 ",[15,940,17],{},[22,942,943],{},"일반적인 Spring 트랜잭션은 현재 Thread에 연결된다. 따라서 호출자 Thread의 트랜잭션이 새 비동기 Thread로 자동 전달되지 않는다.",[29,945,947],{"className":185,"code":946,"language":187,"meta":35,"style":35},"@Transactional\npublic void register(User user) {\n    userRepository.save(user);\n    emailService.sendWelcomeEmail(user.getEmail());\n}\n",[15,948,949,954,959,964,969],{"__ignoreMap":35},[191,950,951],{"class":193,"line":194},[191,952,953],{},"@Transactional\n",[191,955,956],{"class":193,"line":200},[191,957,958],{},"public void register(User user) {\n",[191,960,961],{"class":193,"line":206},[191,962,963],{},"    userRepository.save(user);\n",[191,965,966],{"class":193,"line":212},[191,967,968],{},"    emailService.sendWelcomeEmail(user.getEmail());\n",[191,970,971],{"class":193,"line":309},[191,972,215],{},[29,974,977],{"className":975,"code":976,"language":34,"meta":35},[32],"요청 Thread\n→ register()의 트랜잭션\n\n비동기 Thread\n→ 기존 트랜잭션을 자동으로 이어받지 않음\n",[15,978,976],{"__ignoreMap":35},[22,980,981],{},"비동기 메서드에서 별도의 DB 트랜잭션이 필요하면 해당 비동기 Bean의 메서드에 트랜잭션 경계를 따로 설정한다.",[22,983,984,985,988],{},"또한 ",[15,986,987],{},"register()","가 커밋되기 전에 비동기 작업이 시작될 수 있다. 반드시 커밋 후 실행해야 하는 작업이라면 트랜잭션 이벤트와 메시징 같은 방법을 고려해야 한다.",[45,990],{},[48,992,994],{"id":993},"_9-언제-사용하면-좋은가","9. 언제 사용하면 좋은가",[53,996,997,1007],{},[56,998,999],{},[59,1000,1001,1004],{},[62,1002,1003],{},"적합한 작업",[62,1005,1006],{},"주의할 작업",[72,1008,1009,1017,1025,1033],{},[59,1010,1011,1014],{},[77,1012,1013],{},"이메일과 알림 발송",[77,1015,1016],{},"결과가 즉시 필요한 작업",[59,1018,1019,1022],{},[77,1020,1021],{},"독립적인 외부 API 호출",[77,1023,1024],{},"하나의 트랜잭션으로 묶여야 하는 DB 작업",[59,1026,1027,1030],{},[77,1028,1029],{},"이미지 변환과 보고서 생성",[77,1031,1032],{},"실패하면 반드시 즉시 사용자에게 알려야 하는 작업",[59,1034,1035,1038],{},[77,1036,1037],{},"부가적인 로그 처리",[77,1039,1040],{},"순서 보장이 중요한 작업",[22,1042,1043,1045],{},[15,1044,17],{},"는 단일 서버의 메모리와 Thread Pool에 작업을 맡긴다. 서버가 종료되면 아직 처리하지 못한 작업을 잃을 수 있다. 반드시 처리해야 하는 작업은 Kafka, RabbitMQ 같은 메시지 시스템이나 별도의 작업 Queue가 더 적합할 수 있다.",[45,1047],{},[48,1049,1051],{"id":1050},"핵심-정리","핵심 정리",[53,1053,1054,1064],{},[56,1055,1056],{},[59,1057,1058,1061],{},[62,1059,1060],{},"개념",[62,1062,1063],{},"한 줄 정리",[72,1065,1066,1074,1083,1092,1100,1109,1117],{},[59,1067,1068,1071],{},[77,1069,1070],{},"비동기",[77,1072,1073],{},"완료를 기다리지 않고 다른 실행 흐름에 작업을 맡김",[59,1075,1076,1080],{},[77,1077,1078],{},[15,1079,246],{},[77,1081,1082],{},"Spring의 비동기 어노테이션 처리 활성화",[59,1084,1085,1089],{},[77,1086,1087],{},[15,1088,17],{},[77,1090,1091],{},"메서드 작업을 TaskExecutor에 제출",[59,1093,1094,1097],{},[77,1095,1096],{},"TaskExecutor",[77,1098,1099],{},"비동기 Task와 Thread를 관리",[59,1101,1102,1106],{},[77,1103,1104],{},[15,1105,158],{},[77,1107,1108],{},"나중에 완료될 결과와 후속 작업 표현",[59,1110,1111,1114],{},[77,1112,1113],{},"자기 호출",[77,1115,1116],{},"같은 객체 내부 호출은 Async Proxy를 거치지 않음",[59,1118,1119,1122],{},[77,1120,1121],{},"트랜잭션",[77,1123,1124],{},"호출자 Thread의 트랜잭션이 비동기 Thread로 자동 전달되지 않음",[19,1126,1127],{},[22,1128,1129,1131],{},[15,1130,17],{},"의 핵심은 작업을 무조건 빠르게 만드는 것이 아니라, 기다리는 작업을 별도 실행 흐름에 맡기는 것이다.",[48,1133,1135],{"id":1134},"참고-자료","참고 자료",[1137,1138,1139],"ul",{},[1140,1141,1142],"li",{},[1143,1144,1148],"a",{"href":1145,"rel":1146},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fintegration\u002Fscheduling.html",[1147],"nofollow","Spring Framework Task Execution and Scheduling",[1150,1151,1152],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":35,"searchDepth":200,"depth":200,"links":1154},[1155],{"id":12,"depth":200,"text":5,"children":1156},[1157,1158,1160,1162,1163,1164,1165,1166,1168,1169,1170],{"id":50,"depth":206,"text":51},{"id":172,"depth":206,"text":1159},"2. @Async 활성화",{"id":277,"depth":206,"text":1161},"3. 가장 간단한 @Async",{"id":489,"depth":206,"text":490},{"id":581,"depth":206,"text":582},{"id":754,"depth":206,"text":755},{"id":834,"depth":206,"text":835},{"id":937,"depth":206,"text":1167},"8. 트랜잭션과 @Async",{"id":993,"depth":206,"text":994},{"id":1050,"depth":206,"text":1051},{"id":1134,"depth":206,"text":1135},[187,1172],"spring","2026-08-22 09:00:00 +0900","동기와 비동기, Thread와 Executor의 차이를 이해하고 Spring @Async의 설정, 반환값, 예외와 트랜잭션 경계를 정리한다.","md",null,"\u002Fjava\u002Fspring\u002F2026\u002F08\u002F22\u002Fspring-async\u002F",{"layout":1179},"post","10","\u002Fposts\u002Fbackend\u002Fspring\u002Fspring\u002F2026-08-22-spring-async",{"title":5,"description":1174},"Spring 기초","posts\u002FBackend\u002FSPRING\u002FSpring\u002F2026-08-22-spring-async",[1186,1187,1188,1189,158,1096,112],"Java","Spring","Async","EnableAsync","MoKRifTyrkkQFwhi7vldVGhvX6rHWSqbiieB0ti93XI",1788744786484]