순환 참조 에러

헨도·2025년 1월 6일
0

SpringBoot

목록 보기
22/23
post-thumbnail

풋살장 예약 API 내 구장 예약에 관해 로직을 작성하는 도중 순환 참조 에러가 발생했다...

문제 발생

에러메시지

The dependencies of some of the beans in the application context form a cycle:

   reserveController defined in file [reserve-field/build/classes/java/main/com/example/reservefield/controller/ReserveController.class]
┌─────┐
|  reserveService defined in file [reserve-field/build/classes/java/main/com/example/reservefield/domain/reserve/ReserveService.class]
↑     ↓
|  stadiumService defined in file [reserve-field/build/classes/java/main/com/example/reservefield/domain/stadium/StadiumService.class]
└─────┘

Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

프로젝트 구조

내가 작성한 프로젝트는 도메인 별 관련 DB는 해당 서비스안에서 해결한 후에 불러오는 방법을 사용했다.
(reserveRepository 에 해당하는 Query 는 reserveService 에서 메서드로 만든 후, StadiumService 등 다른 Service 에서 사용했다.)

그 과정에서 StadiumService 에는 ReserveService 가 선언되고, ReserveService 에는 StadiumService 가 선언되어 각각의 파일에서 불러오고 불러오다보니 순환 에러가 발생한 것이다.

ReserveService

StadiumService

문제 해결 고민

1. 하위 단계 참조

현재 문제는 Service -> Service 의 같은 단계에서 순환되어 발생되는 문제이다.

그럼 Service 부분의 1단계 아래인 Repository 를 사용하면 어떨까?

기존 코드

(StadiumService)
	@Transactional
    public Stadium getStadiumById(Long id) {
        return stadiumRepository.findById(id)
            .orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
    }

(ReserveService)
...
	Stadium stadium = stadiumService.getStadiumById(stadiumId);
...

변경 코드

(ReserveService)
...
	Stadium stadium = stadiumRepository.findById(id)
    	.orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
...

위처럼 처리하면 각각의 하위 단계인 Repository 파일에서 다른 Repository 를 참조하여 사용하지 않기 때문에 원활한 처리가 가능하다.

2. @Lazy + 참조 Service 파일 분리'

위 사진처럼 Component Service 파일을 하나두고 그 안에 각각의 Service 를 @Lazy 로 불러오는 방법을 사용하면 각각의 Service 파일에서 불러올 수 있다.

CircularService 파일 추가

@Getter
@Component
public class CircularService {
    private final StadiumService stadiumService;
    private final ReserveService reserveService;

    public CircularService(
        @Lazy StadiumService stadiumService,
        @Lazy ReserveService reserveService
    ) {
        this.stadiumService = stadiumService;
        this.reserveService = reserveService;
    }
}

기존 코드

(StadiumService)
	@Transactional
    public Stadium getStadiumById(Long id) {
        return stadiumRepository.findById(id)
            .orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
    }

(ReserveService)
...
	Stadium stadium = stadiumService.getStadiumById(stadiumId);
...

변경 코드

(StadiumService)
	@Transactional
    public Stadium getStadiumById(Long id) {
        return stadiumRepository.findById(id)
            .orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, "구장 정보가 존재하지 않습니다."));
    }

(ReserveService)
...
	Stadium stadium = Stadium stadium = circularService.getStadiumService().getStadiumById(reserve.getStadium().getId());
...

결론

Repository 를 이용하여 처리하는게 수월해보이지만,프로젝트 내 상황을 보았을 때, Image 를 처리하는 과정에서 ImageService 내 사진을 저장하는 메서드를 꼭 불러와 사용하여야 되기 때문에 CircularService 파일을 만들어 @Lazy 로 처리하는 방식을 사용했다.

사용하려는 Service 를 매번 추가해주는 것을 감안하면 조금 복잡하지만, 프로젝트 내 상황을 고려하면 이게 맞다고 생각했다.

프로젝트에 맞는 방식을 고려하여 채택하는 것도 중요하니 각자의 최선을 방식을 고려해보고 선택했으면 좋겠다.

profile
Junior Backend Developer

0개의 댓글