스프링 부트 순환 참조 해결하기

Hannana·2024년 11월 28일

서로 다른 클래스가 각자의 Bean을 생성하기 위해 서로를 참조하다가
결국 어느 Bean도 생성하지 못하는 문제이다.
(빈 생성 충돌)

사건의 경위

  • @Transactional 어노테이션이 붙은 메서드를 같은 클래스 내에서 호출하면 AOP 프록시 객체가 우회하여 트랜잭션이 작동되지 않는 현상이 발생
  • 스프링 AOP(Aspect-Oriented Programming) 패러다임을 따라 클래스를 분리함
  • 비즈니스 로직을 담당하는 서비스 코드와 트랜잭션 메서드를 관리하는 코드 분리

그래서 적용 된
기존의 내 서비스 코드는

+---------------------------+
|      EduCardService       |
|---------------------------|
| - Redis 캐싱 로직         |
| - 크롤링 로직             |
| - RDS 데이터 저장 요청    | <----+
+---------------------------+      |
          |                         |
          v                         |
+---------------------------+       |
| EduCardTransactionalService |
|---------------------------|       |
| - RDS 데이터 저장          |       |
| - RDS 데이터 조회          |       |
| - 크롤링 요청 처리         | ------+
+---------------------------+

이렇게 서로를 참조하는 형태가 되었다.
Redis 캐싱 체크하고, -> 없으면 RDS를 조회해 캐싱에 가져다 놓고..
RDS에 없으면 -> 크롤링 로직 돌리고 -> RDS에 저장하고..

@Service
@RequiredArgsConstructor
public class EduCardService {
    private final EduCardTransactionalService transactionalService;

    public List<EduCardDto> fetchEduCardContentsWithCache() {
        // Redis → RDS → Crawling
        return transactionalService.handleCrawlingAndSave();
    }
}

@Service
@RequiredArgsConstructor
public class EduCardTransactionalService {
    private final EduCardService eduCardService;

    public List<EduCardDto> handleCrawlingAndSave() {
        return eduCardService.crawlEduCardContents(); //크롤링 요청 to EduCardService 
    }
}

위 코드보면 알 수 있듯이 왔다갔다.. 그러다보니 순환 참조 문제가 발생하게 되었다.
빈 생성이 온전히 이뤄지도록 하기 위해서 순환 참조를 제거해주어야 하고,
클래스의 책임을 명확히 분리해야 한다.

EduCardService
-캐싱 및 크롤링만 담당
-데이터베이스 관련 작업은 하지 않음

EduCardTransactionalService
-데이터베이스 관련 작업만 담당
-크롤링 로직은 호출하지 않음

이렇게 클래스의 역할을 완전히 분리하려고 한다.
서로를 참조하는 형태에서, 하나를 완전히 모듈화하여 단방향 호출하도록 바꿔준다.

@Service
@RequiredArgsConstructor
public class EduCardService {
    private final EduCardTransactionalService transactionalService;

    public List<EduCardDto> fetchEduCardContentsWithCache() {
        // Redis → RDS → Crawling
        transactionalService.saveCrawledData(crawlEduCardContents());
    }
}

@Service
@RequiredArgsConstructor
public class EduCardTransactionalService {
    // 데이터 저장 및 조회만 담당
    public void saveCrawledData(List<EduCardDto> crawledData) {
        // 크롤링 된 데이터를 RDS에 저장
    }
}
profile
(구) https://hansjour.tistory.com/ 이사옴. 성장하는 하루를 쌓아가는 블로그

0개의 댓글