본문 바로가기

WEB/Spring

[Spring] Caused by: java.lang.IllegalStateException: Duplicate key 에러, Swagger GroupedOpenApi 사용

현재 저는 팀원들과 두번쨰 프로젝트를 진행하고 있습니다. 프론트엔드 1명, 백엔드 3명 풀스택 2명으로 구성되어 있으며

저는 풀스택을 맡게되었습니다...허허...

프로젝트의 주제는 여행용 모임통장입니다.

다음과 같이 진행중입니다.

  • Java 17
  • Spring Boot 3.2.0
  • Gradle
  • React
문제
Caused by: java.lang.IllegalStateException: Duplicate key 계좌 
(attempted merging values org.springdoc.webmvc.api.OpenApiWebMvcResource@1602ab86 and 
org.springdoc.webmvc.api.OpenApiWebMvcResource@7707c2bb)

 

문제는 모임통장 생성기능 구현 중 일어났습니다. SpringDoc은 swagger를 사용하기 위해 의존성을 추가해 놓은 것 인데 swagger 관련 오류겠구나 싶었습니다.

 

의아했던점은 계좌 기능 구현 후 정상작동 확인을 다 하고 모임통장으로 넘어왔는데 'Duplicate key 계좌' 라는 내용이 찍혀있는점 입니다.

일단 실수 확률이 높은 Endpoint 중복여부를 확인하였습니다.

 

모임 통장 컨트롤러

@Tag(name = "모임통장 컨트롤러", description = "Group Account Controller API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/groupaccount")
public class GroupAccountController {

    private final ApiResponse response;
    private final GroupAccountService groupAccountService;

    @Operation(summary = "모임 통장 생성", description = "모임 통장 생성")
    @PostMapping
    public ResponseEntity<?> createGroupAccount(@RequestBody GroupAccountPostDto groupAccountPostDto){
        Long groupAccountId = groupAccountService.createGroupAccount(groupAccountPostDto);
        return response.success(ResponseCode.GROUP_ACCOUNT_CREATED, groupAccountId);
    }
}

 

계좌 컨트롤러

@Tag(name = "Account 컨트롤러", description = "Account Controller API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/account")
public class AccountController {

    private final ApiResponse response;
    private final AccountService accountService;
//    private final MemberService memberService;

    @Operation(summary = "계좌 생성", description = "계좌 생성")
    @PostMapping
    public ResponseEntity<?> createAccount(@RequestBody AccountPostDto accountPostDto) {
        Long accountId = accountService.createAccount(accountPostDto);
        return response.success(ResponseCode.ACCOUNT_CREATED, accountId);
    }

    @Operation(summary = "멤버별 계좌 조회", description = "멤버별 계좌 조회")
    @GetMapping("/my/{memberId}")
    public ResponseEntity<?> getMyAccountList(@PathVariable Long memberId){
        List<AccountInfoDto> accountInfoList = accountService.getMyAccountList(memberId);
        if(accountInfoList.isEmpty()){
            return response.success(ResponseCode.ACCOUNT_LIST_NOT_FOUND, null);
        };
        return response.success(ResponseCode.ACCOUNT_LIST_FETCHED, accountService.getMyAccountList(memberId));
    }

    @Operation(summary = "계좌 잔액 업데이트", description = "계좌 잔액 업데이트")
    @PutMapping
    public ResponseEntity<?> updateAmount(@RequestBody AccountUpdateDto accountUpdateDto){
        Long accountId = accountService.updateAmount(accountUpdateDto);
        return response.success(ResponseCode.ACCOUNT_INFO_UPDATED, accountId);
    }
}

 

※ 아직 개발 중이고 다른 팀원들의 코드와 상호작용을 하지 못해 코드가 좀 귀여울 수 있습니다.

겹치는 EndPoint는 없는것으로 확인되었습니다. 사실 RequestMapping을 설정해 놓았기에 거의 그럴일이 없긴했습니다.

 

성공 시 응답메세지를 관리하는 ResponseCode 에서도 별다른 점은 없었습니다.

 

 

 

swagger 테스트

 

문제 해결

 

문제는 SwaggerConfig에 있었습니다.

현재 저희는 Swagger를 분류하기 위해 GroupedOpenApi를 사용하고 있습니다.

 

Select a definition을 사용해서 컨트롤러를 분류해놓고 확인 할 수 있습니다.

 

문제가 된 부분은 다음과 같습니다.

 

복사해서 사용하다 보니 group이름이 중복되어 있었습니다. 

 

수정 전

@Bean
    public GroupedOpenApi accountGroup() {
        return GroupedOpenApi.builder()
                .group("계좌")
                .pathsToMatch("/api/v1/account/**")
                .build();
    }
    @Bean
    public GroupedOpenApi groupAccountGroup() {
        return GroupedOpenApi.builder()
                .group("계좌")
                .pathsToMatch("/api/v1/groupaccount/**")
                .build();
    }

 

수정 후

@Bean
    public GroupedOpenApi accountGroup() {
        return GroupedOpenApi.builder()
                .group("계좌")
                .pathsToMatch("/api/v1/account/**")
                .build();
    }
    @Bean
    public GroupedOpenApi groupAccountGroup() {
        return GroupedOpenApi.builder()
                .group("모임통장")
                .pathsToMatch("/api/v1/groupaccount/**")
                .build();
    }

 

해당 부분을 수정하고 다시 해보니 정상작동 하는 것을 확인할 수 있었습니다.


혹시라도 Swagger에서 GroupedOpenApi를 사용하길 원하시는 분들도 있을것같아 SwaggerConfig 레퍼런스 코드 남깁니다.

package com.noah.backend.global.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@OpenAPIDefinition(
        info = @Info(title = "NOAH API 명세서",
                description = "NOAH 서비스 API 명세서",
                version = "v1"))
@Configuration
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi all() {
        return GroupedOpenApi.builder()
                .group("전체")
                .pathsToMatch("/api/**")
                .build();
    }

    @Bean
    public GroupedOpenApi bankGroup() {
        return GroupedOpenApi.builder()
                .group("은행")
                .pathsToMatch("/api/v1/bank/**")
                .build();
    }
    @Bean
    public GroupedOpenApi accountGroup() {
        return GroupedOpenApi.builder()
                .group("계좌")
                .pathsToMatch("/api/v1/account/**")
                .build();
    }
    @Bean
    public GroupedOpenApi groupAccountGroup() {
        return GroupedOpenApi.builder()
                .group("모임통장")
                .pathsToMatch("/api/v1/groupaccount/**")
                .build();
    }
    @Bean
    public GroupedOpenApi travelGroup() {
        return GroupedOpenApi.builder()
                .group("여행")
                .pathsToMatch("/api/v1/travel/**")
                .build();
    }

    @Bean
    public GroupedOpenApi reviewGroup() {
        return GroupedOpenApi.builder()
                .group("리뷰")
                .pathsToMatch("/api/v1/review/**")
                .build();
    }
    @Bean
    public GroupedOpenApi commentGroup() {
        return GroupedOpenApi.builder()
                .group("댓글")
                .pathsToMatch("/api/v1/comment/**")
                .build();
    }
}

 


초보 개발자의 글이라 부족한 점이 많습니다. 잘못된 점 등을 말씀해주시면 감사히 받겠습니다.