분홍색/빨간색보다 노란 장미가 제일 예뻐요🌟
1. 목표🎯
: 특정 카테고리를 삭제할 수 있는 기능 구현하기
2. 기능 명세서📜
Name | Method | URI | Domain | AuthZ |
(특정) 카테고리 삭제 | DELETE | /category/{categoryId} | category | ADMIN |
- AuthZ는 권한 검사. ADMIN(관리자) 계정만 이용할 수 있는 기능임을 명시.
3. 1차 코드 작성💻
3.1 CategoryController
// CategoryController
/**
* 특정 카테고리 삭제
*/
@PreAuthorize("hasRole('ADMIN')") // ADMIN 검사
@DeleteMapping("/{categoryId}")
public ResponseEntity<Void>deleteCategory(@PathVariable Long categoryId) {
categoryService.deleteCategory(categoryId);
return ResponseEntity.ok().build();
}
삭제할 카테고리는 이미 uri에서 표시 중(/{categoryId}).
따라서 @PathVariable을 사용하여 별도의 Response를 통해 카테고리ID를 받을 필요 없다.
@PathVariable
- 경로 변수를 표시하기 위해 메서드에 매개변수에 사용된다.
- 경로 변수는 중괄호 {id}로 둘러싸인 값을 나타낸다
- url 경로에서 변수 값을 추출하여 매개변수에 할당한다.
- 기본적으로 경로 변수는 반드시 값을 가져야 하며, 값이 없는 경우 404 오류가 발생한다.
- 주로 상세 조회, 수정, 삭제와 같은 작업에서 리소스 식별자로 사용된다.
3.2 CategoryService
// CategoryService
/**
* 특정 카테고리 삭제
*/
@Transactional
public void deleteCategory(Long categoryId) {
Category category = categoryRepository.findById(categoryId)
.orElseThrow(() -> new CustomException(CATEGORY_NOT_FOUND));
categoryRepository.deleteById(categoryId);
}
먼저 해당 카테고리가 존재하는지 확인한다. 존재하지 않는다면 CATEGORY_NOT_FOUND라는 CustomException 발생시키기.
기본적으로 제공하는 CRUD 기능을 이용하여 카테고리 삭제
3.3 CategoryRepository
// CategoryRepository
@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Optional<Category> findById(Long categoryId);
void deleteById(Long categoryId);
}
JpaRepository를 상속받아야 한다!
4. 오류...🤯
4.1 오류가 두 개나..
첫 번째 오류
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement [Referential integrity constraint violation: "FK2N9W8D0DP4BSFRA9DCG0046L4: PUBLIC.ITEM FOREIGN KEY(CATEGORY_ID) REFERENCES PUBLIC.CATEGORY(CATEGORY_ID) (CAST(4 AS BIGINT))"; SQL statement:
두 번째 오류
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FK2N9W8D0DP4BSFRA9DCG0046L4: PUBLIC.ITEM FOREIGN KEY(CATEGORY_ID) REFERENCES PUBLIC.CATEGORY(CATEGORY_ID) (CAST(4 AS BIGINT))"; SQL statement:
4.2 원인 살펴보기
먼저 첫 번째 오류부터 알아보자. 챗지피티 짱!ㅎㅎ
이 에러 메시지는 Hibernate에서 발생한 ConstraintViolationException으로, 데이터베이스의 참조 무결성 제약 조건을 위반했음을 나타냅니다. 구체적으로는 외래 키 제약 조건이 위반되었음을 의미합니다.
메시지의 주요 부분을 살펴보면:
- "Referential integrity constraint violation": 참조 무결성 제약 조건 위반.
- "FK2N9W8D0DP4BSFRA9DCG0046L4": 위반된 외래 키 제약 조건의 이름.
- "PUBLIC.ITEM FOREIGN KEY(CATEGORY_ID) REFERENCES PUBLIC.CATEGORY(CATEGORY_ID)": ITEM 테이블의 CATEGORY_ID 외래 키가 CATEGORY 테이블의 CATEGORY_ID를 참조하고 있다는 의미.
- "(CAST(4 AS BIGINT))": 외래 키 제약 조건에 의해 참조하려는 CATEGORY_ID 값이 4인 상황.
이 에러는 ITEM 테이블에 삽입하거나 업데이트하려는 데이터가 CATEGORY 테이블에 존재하지 않는 CATEGORY_ID를 참조하고 있을 때 발생합니다. 즉, ITEM 테이블의 CATEGORY_ID가 CATEGORY 테이블에 존재하지 않는 값을 참조하고 있기 때문에, 데이터베이스가 무결성을 유지할 수 없어 에러가 발생한 것입니다.
해결 방법은 다음과 같습니다:
1. 참조 무결성 확인: ITEM 테이블에 삽입하려는 데이터의 CATEGORY_ID가 실제로 CATEGORY 테이블에 존재하는지 확인합니다.
2. 데이터 검증: CATEGORY_ID가 올바른지, CATEGORY 테이블에 맞는 데이터가 있는지 검토합니다.
3. 데이터 정합성: CATEGORY 테이블에 필요한 데이터가 누락되지 않았는지 확인하고, 필요시 데이터를 추가하거나 수정합니다.
이렇게 하면 외래 키 제약 조건 위반 문제를 해결할 수 있을 것입니다.
그런데 여기서 의문. 나는 category를 삭제하려는데 왜 자꾸 item얘기가 나오는 거지?🤔
item과 관련된 코드는 하나도 건드리지 않았는데??!
카테고리를 삭제할 때 item 얘기가 나오는 이유는 ITEM 테이블이 CATEGORY 테이블을 참조하고 있기 때문입니다. 즉, ITEM 테이블의 CATEGORY_ID 컬럼이 CATEGORY 테이블의 CATEGORY_ID를 외래 키로 참조하고 있어서 카테고리 삭제 시 ITEM 테이블의 참조 무결성을 고려해야 합니다.
쉽게 얘기하자면, category와 item은 서로 연결되어 있다. 그런데 이 상황에서 category를 삭제해버리니까 item이 길 잃고 남겨졌다는 문제이다!!
정말 생각지도 못한 오류였다.... 카테고리를 삭제할 때, 카테고리'만' 삭제해서는 안된다니.
당연히 카테고리를 삭제하면 그 안에 속하는 아이템까지 자동적으로 삭제해주는 줄 알았다.
4.3 해결책 구상하기
1. 카테고리 삭제하기 전에, item부터 모두 삭제한다.
2. 그 후 category를 삭제 : 연결된 item이 없으므로 안전하게 삭제 가능!
3. 수정사항 db에 반영해주기
5. 해결!!🥳
6. 최종 코드🍀
CategoryController는 변경사항 없음.
6.1 CategoryService
// CategoryService
/**
* 특정 카테고리 삭제
*/
@Transactional
public void deleteCategory(Long categoryId) {
Category category = categoryRepository.findById(categoryId)
.orElseThrow(() -> new CustomException(CATEGORY_NOT_FOUND));
// 카테고리와 관련된 아이템을 먼저 삭제
itemRepository.deleteByCategoryId(categoryId);
// 그 후에 카테고리 삭제
categoryRepository.deleteById(categoryId);
}
카테고리에 속하는 아이템을 삭제하는 코드! 딱 한 줄만 추가되었다!!
6.2 ItemRepository
// ItemRepository
public interface ItemRepository extends JpaRepository<Item, Long> {
@Modifying
@Query("DELETE FROM Item i WHERE i.category.id = :categoryId")
void deleteByCategoryId(@Param("categoryId") Long categoryId);
}
특정 카테고리를 삭제하는 api는 비교적 빠르게 작성했다!!
오류가 처음에 두 개가 떴었는데, 첫 번째 오류를 해결하다보니 두 번째 오류도 자연스럽게 해결되었다.
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FK2N9W8D0DP4BSFRA9DCG0046L4: PUBLIC.ITEM FOREIGN KEY(CATEGORY_ID) REFERENCES PUBLIC.CATEGORY(CATEGORY_ID) (CAST(4 AS BIGINT))"; SQL statement:
그래도 무슨 오류였는지 알아나 보자.
JdbcSQLIntegrityConstraintViolationException는 데이터베이스에서 외래 키 제약 조건을 위반할 때 발생하는 오류입니다. 메시지에서 Referential integrity constraint violation이라고 명시된 것은 외래 키 제약 조건이 충족되지 않았음을 의미합니다. 특히, FK2N9W8D0DP4BSFRA9DCG0046L4는 데이터베이스에서 정의된 외래 키 제약 조건의 이름입니다.
문제 분석
이 오류는 ITEM 테이블의 CATEGORY_ID 열이 CATEGORY 테이블의 CATEGORY_ID 열과 연결되어 있을 때 발생합니다. CATEGORY_ID를 참조하는 ITEM 레코드가 존재할 때, CATEGORY 레코드를 삭제하려고 하면 외래 키 제약 조건에 의해 삭제가 차단됩니다.
첫 번째 오류와 결국 같은 오류였다!
'PROJECT > GDSC 프로젝트 트랙 : 물품 대여 서비스' 카테고리의 다른 글
[UXUI] 이젠 멀리서도 간편하게, 물품 대여 서비스 '와우대여' (5) | 2024.08.17 |
---|---|
[SpringBoot] 특정 카테고리 정보 조회 API 구현 (0) | 2024.08.14 |
[SpringBoot] 특정 물품 정보 조회 API 구현 (0) | 2024.08.14 |
[SpringBoot] 물품 추가 API 구현 (0) | 2024.08.13 |