[오늘가챠] 비동기(@Async)의 늪: 외부 통신 중 마주친 JPA Detached Entity 해결기

2026. 8. 22. 16:40·◈ Yermi Project/오늘가챠🃏
728x90
반응형

업로드된 이미지를 Cloudinary(미디어 서버)에 전송하고, AI(Gemini API)로 분석하는 작업은 네트워크 상황에 따라 2~5초가량 소요됩니다. 유저를 멍하니 5초 동안 멈춰둔 채 기다리게 할 수 없어, 응답성을 높이고자 로직을 Spring @Async를 활용해 비동기로 분리하려다 영속성 컨텍스트 지옥에 빠진 트러블슈팅 경험입니다.


JPA Detached Entity 문제 흐름


1. 섣부른 비동기 처리의 시도

요청이 오면 "업로드 대기열에 등록되었습니다"라는 성공 응답을 클라이언트에 0.1초 만에 내려주고, 무거운 작업은 백그라운드 스레드에 던져버리도록 설계했습니다.

// ❌ 처음 시도한 코드 (에러 발생)
@Async // 새로운 백그라운드 스레드풀에서 실행
public void processUploadAsync(User user, byte[] file) {
    // 1. 3~5초가 걸리는 외부 API 통신 (Blocking)
    String url = cloudinaryService.upload(file);

    // 2. 💥 에러 발생 포인트!
    Card card = new Card(user, url); 
    cardRepository.save(card);
}

 

결과는 대실패였습니다. 업로드는 잘 되었으나 마지막에 DB 저장 시 Detached Entity 에러가 발생하며 롤백되었습니다.

org.hibernate.LazyInitializationException: 
  could not initialize proxy [com.gacha.user.entity.User#1] - no Session

2. 원인 파악: 스레드(ThreadLocal)와 OSIV의 한계

Spring Data JPA는 기본적으로 웹 요청(Request)을 처리하는 단일 스레드(ThreadLocal)에 트랜잭션과 영속성 컨텍스트(EntityManager)를 바인딩하여 묶어둡니다. (Open Session In View 패턴 포함)

[시간순 흐름]
Main Thread ──────────────────────────────────────────
 ┌────────────────────────────────────┐
 │  트랜잭션 열림 + 영속성 컨텍스트     │
 │  1. User 조회 (Managed 상태)       │
 │  2. @Async 호출 (User 객체 전달)    │
 │  3. HTTP 200 OK 반환               │
 └────────────────────────────────────┘
   ↑ 트랜잭션 종료, 영속성 컨텍스트 닫힘

Async Thread ──────────────────────────────────────────
           ┌─────────────────────────────────────┐
           │  4. Cloudinary 업로드 (3초 대기)     │
           │  5. user.getCards()... → 💥 에러!    │
           │     User는 이미 Detached 상태!       │
           └─────────────────────────────────────┘
  1. 메인 스레드에서 유저(User) 객체를 조회해 비동기 메서드로 파라미터로 넘겼습니다.
  2. 메인 스레드는 즉시 200 OK를 반환하고 트랜잭션을 종료하며 영속성 컨텍스트를 닫아버립니다.
  3. 새로운 백그라운드 스레드에서 수 초의 외부 API 통신이 끝난 뒤, 넘겨받은 User 객체를 이용해 새로운 릴레이션을 맺으려 시도합니다.
  4. 그러나 JPA 입장에서 해당 User는 이미 관리 대상이 아닌 준영속(Detached) 상태의 껍데기 객체입니다. 따라서 저장을 거부한 것입니다.

3. 해결책: 트랜잭션 분리와 식별자(ID) 전달 패턴

엔티티 객체 자체를 다른 스레드의 파라미터로 넘기는 것은 시한폭탄을 넘기는 것과 같습니다. 객체 대신 엔티티의 식별자(PK ID)만 넘기고, 비동기 스레드 내부에서 완전히 독립된 새로운 트랜잭션을 열어 다시 로드(Proxy 초기화)하도록 리팩토링했습니다.

// ✅ 해결된 코드 — ID만 전달하고 새 트랜잭션에서 재조회

@Async
public void processUploadAsync(Long userId, byte[] file) {
    // 외부 API 통신: 여기는 트랜잭션을 걸지 않아 커넥션 낭비를 막음
    String url = cloudinaryService.upload(file);

    // DB 저장: 새로운 스레드 안에서 별도 트랜잭션 개시
    saveCardTransaction(userId, url);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveCardTransaction(Long userId, String url) {
    // 새로운 영속성 컨텍스트에 엔티티를 새롭게 로드(Attached)
    User user = userRepository.findById(userId).orElseThrow();
    Card card = new Card(user, url); 
    cardRepository.save(card); // ✅ 정상 저장!
}

4. 마무리

비동기 처리는 유저의 응답 시간을 비약적으로 개선하지만, JPA의 스레드 로컬 바인딩 특성과 생명주기를 완벽하게 이해하지 못하면 런타임 에러의 원흉이 됩니다. 비동기 큐잉을 할 때는 파라미터로 순수한 식별자(Long)나 불변 객체(DTO)만 전달해야 한다는 개발 패턴을 뼈저리게 배울 수 있었습니다.

728x90
반응형
'◈ Yermi Project/오늘가챠🃏' 카테고리의 다른 글
  • [오늘가챠] 성능 끌어올리기: CompletableFuture를 이용한 병렬 처리
  • [오늘가챠] 앱 생태계를 지켜라: dHash와 SHA-256을 활용한 이미지 중복 방어
  • [오늘가챠] 생성형 AI의 도입: Gemini 2.0 Flash와 안전장치
  • [오늘가챠] 프론트-DB 직결 시대의 필수 보안: Supabase RLS 적용기
예르미(yermi)
예르미(yermi)
끊임없이 제 자신을 계발하는 개발자입니다👨🏻‍💻
  • 예르미(yermi)
    예르미의 코딩노트
    예르미(yermi)
  • 전체
    오늘
    어제
    • 분류 전체보기 (1024) N
      • ◎ Java (133)
        • Java☕ (93)
        • JSP📋 (26)
        • Applet🧳 (6)
        • Interview👨🏻‍🏫 (8)
      • ◎ JavaScript (48)
        • JavaScript🦎 (25)
        • jQuery🌊 (8)
        • React🌐 (2)
        • Vue.js🔰 (6)
        • Node.js🫒 (3)
        • Google App Script🐑 (4)
      • ◎ HTML5+CSS3 (17)
        • HTML5📝 (8)
        • CSS3🎨 (9)
      • ──────────── (0)
      • ▣ Framework (67)
        • Spring🍃 (36)
        • Spring Boot🍀 (12)
        • Bootstrap💜 (3)
        • Selenium🌕 (6)
        • MyBatis🐣 (10)
      • ▣ Tools (47)
        • API🎯 (18)
        • Library🎲 (15)
        • JitPack🚀 (3)
        • Jenkins👨🏻 (7)
        • Thymeleaf🌿 (4)
      • ▣ Server (30)
        • Apache Tomcat🐱 (14)
        • Apache HTTP Server🛡️ (1)
        • Nginx🧶 (7)
        • OracleXE💿 (4)
        • VisualSVN📡 (4)
      • ▣ Infra & DevOps (19) N
        • LGTM Stack🔭 (5)
        • Kafka🐦‍🔥 (0)
        • Kubernetes🚢 (8)
        • KubeCon Japan 2026⚓ (6) N
      • ▣ OS : 운영체제 (18)
        • cmd : 명령프롬프트💻 (10)
        • Linux🐧 (8)
      • ▣ SQL : Database (56)
        • Oracle SQL🏮 (26)
        • PL SQL💾 (9)
        • MySQL🐬 (6)
        • MariaDB🦦 (6)
        • H2 Database🔠 (3)
        • SQL 실전문제🐌 (6)
      • ────────── (0)
      • ◈ Human Project (86)
        • Mini : Library Service📚 (15)
        • 화면 설계 [HTML]🐯 (10)
        • 서버 프로그램 구현🦁 (15)
        • Team : 여수어때🛫 (19)
        • Custom : Student🏫 (9)
        • Custom : Board📖 (18)
      • ◈ Yermi Project (49) N
        • 조사모아(Josa-moa)📬 (5)
        • Riddle-Game🧩 (6)
        • 맛있을 지도🍚 (2)
        • 어디 가! 박대리!🙋🏻‍♂️ (5)
        • 조크베어🐻‍❄️ (4)
        • Looks Like Thirty🦉 (2)
        • Toy Project💎 (12)
        • 오픈소스 파헤치기🪐 (5)
        • 오늘가챠🃏 (8) N
      • ◈ Refactoring (15)
        • Mini : Library Service📚 (8)
        • 서버 프로그램 구현🦁 (1)
        • Team : 여수어때🛫 (0)
        • 쿼리 튜닝일지🔧 (6)
      • ◈ Coding Test (80)
        • 백준(BOJ)👨🏻‍💻 (71)
        • 프로그래머스😎 (2)
        • 코드트리🌳 (7)
      • ◈ Study (129)
        • 기초튼튼 개발지식🥔 (25)
        • HTTP 웹 지식💡 (4)
        • 클린코드(Clean Code)🩺 (1)
        • 디자인패턴(GoF)🥞 (12)
        • 알고리즘(Algorithm)🎡 (14)
        • 다이어그램(Diagram)📈 (4)
        • 파이썬(Python)🐍 (16)
        • 에러노트(Error Note)🧱 (34)
        • 웹 보안(Web Security)🔐 (11)
        • 인공지능 AI🛸 (8)
      • ◈ 공부모임 (57)
        • 혼공학습단⏰ (18)
        • 코드트리 챌린지👊🏻 (2)
        • 개발도서 100독👟 (8)
        • 나는 리뷰어다🌾 (17)
        • 국가기술자격 서포터즈🌻 (12)
      • ◈ 자격증 공부 (48)
        • 정보처리기사🔱 (16)
        • 정보처리산업기사🔅 (9)
        • 정보보안기사⚜️ (11)
        • 컴퓨터활용능력 1급📼 (12)
      • ─────────── (0)
      • ◐ 기타 (124)
        • 알아두면 좋은 팁(tip)✨ (46)
        • 개발자의 일상🎈 (55)
        • 개발도서 서평🔍 (10)
        • 개발관련 세미나🎤 (2)
        • 블로그 꾸미기🎀 (9)
        • 사도신경 프로젝트🎚️ (2)
  • 인기 글

  • 최근 댓글

  • 반응형
    250x250
  • 태그

    Error Note
    SQL
    Java
    꿀팁
    Project
    spring
    자바스크립트
    일상
    백준 티어
    코딩 테스트
    BOJ
    javascript
    jsp
    코딩
    Database
    spring boot
    CSS
    프로그래밍
    백준
    Oracle
  • hELLO· Designed By정상우.v4.10.3
예르미(yermi)
[오늘가챠] 비동기(@Async)의 늪: 외부 통신 중 마주친 JPA Detached Entity 해결기
상단으로

티스토리툴바