Skip to content

Commit ca172a9

Browse files
kangcheolungclaude
andcommitted
fix: 관리자 삭제 기능에 FK 제약 조건 방어 로직 추가
- 브랜드 삭제 시 해당 브랜드를 사용하는 상품이 있으면 삭제 불가 - 카테고리 삭제 시 해당 카테고리를 사용하는 상품이 있으면 삭제 불가 - 카테고리 삭제 시 하위 카테고리가 있으면 삭제 불가 - 카테고리 수정 시 자기 참조(순환 참조) 방지 - 상품 옵션 수정 시 재고 음수 값 방지 - 관련 에러 코드 추가 (BRAND-002, CATEGORY-003~005, PRODUCT-004) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent a93bb22 commit ca172a9

File tree

6 files changed

+41
-0
lines changed

6 files changed

+41
-0
lines changed

src/main/java/com/ongil/backend/domain/admin/service/AdminService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ public void deleteBrand(Long brandId) {
151151
Brand brand = brandRepository.findById(brandId)
152152
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.BRAND_NOT_FOUND));
153153

154+
// 해당 브랜드를 사용하는 상품이 있는지 확인
155+
if (productRepository.existsByBrandId(brandId)) {
156+
throw new ValidationException(ErrorCode.CANNOT_DELETE_BRAND_WITH_PRODUCTS);
157+
}
158+
154159
brandRepository.delete(brand);
155160
}
156161

@@ -180,6 +185,16 @@ public void deleteCategory(Long categoryId) {
180185
Category category = categoryRepository.findById(categoryId)
181186
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.CATEGORY_NOT_FOUND));
182187

188+
// 해당 카테고리를 사용하는 상품이 있는지 확인
189+
if (productRepository.existsByCategoryId(categoryId)) {
190+
throw new ValidationException(ErrorCode.CANNOT_DELETE_CATEGORY_WITH_PRODUCTS);
191+
}
192+
193+
// 하위 카테고리가 있는지 확인
194+
if (categoryRepository.existsByParentCategoryId(categoryId)) {
195+
throw new ValidationException(ErrorCode.CANNOT_DELETE_CATEGORY_WITH_SUBCATEGORIES);
196+
}
197+
183198
categoryRepository.delete(category);
184199
}
185200

src/main/java/com/ongil/backend/domain/category/entity/Category.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ public void updateCategory(String name, String iconUrl, Integer displayOrder, Ca
5858
this.displayOrder = displayOrder;
5959
}
6060
if (parentCategory != null) {
61+
// 자기 참조(순환 참조) 방지
62+
if (parentCategory.getId().equals(this.id)) {
63+
throw new com.ongil.backend.global.common.exception.ValidationException(
64+
com.ongil.backend.global.common.exception.ErrorCode.CATEGORY_SELF_REFERENCE
65+
);
66+
}
6167
this.parentCategory = parentCategory;
6268
}
6369
}

src/main/java/com/ongil/backend/domain/category/repository/CategoryRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,7 @@ public interface CategoryRepository extends JpaRepository<Category, Long> {
3434
"WHERE c.parentCategory.id = :parentCategoryId " +
3535
"ORDER BY c.displayOrder")
3636
List<Category> findSubCategoriesByParentId(@Param("parentCategoryId") Long parentCategoryId);
37+
38+
// 특정 카테고리를 상위 카테고리로 가진 하위 카테고리가 있는지 확인
39+
boolean existsByParentCategoryId(Long parentCategoryId);
3740
}

src/main/java/com/ongil/backend/domain/product/entity/ProductOption.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ public void updateProductOption(String size, String color, Integer stock) {
5454
this.color = color;
5555
}
5656
if (stock != null) {
57+
// 재고 음수 방지
58+
if (stock < 0) {
59+
throw new com.ongil.backend.global.common.exception.ValidationException(
60+
com.ongil.backend.global.common.exception.ErrorCode.INVALID_STOCK
61+
);
62+
}
5763
this.stock = stock;
5864
}
5965
}

src/main/java/com/ongil/backend/domain/product/repository/ProductRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,10 @@ List<Product> findRecommendedProducts(
229229
@EntityGraph(attributePaths = {"brand", "category"})
230230
@Query("SELECT p FROM Product p WHERE p.id IN :productIds AND p.onSale = true")
231231
List<Product> findByIdInAndOnSaleTrue(@Param("productIds") List<Long> productIds);
232+
233+
// 특정 브랜드를 사용하는 상품이 있는지 확인
234+
boolean existsByBrandId(Long brandId);
235+
236+
// 특정 카테고리를 사용하는 상품이 있는지 확인
237+
boolean existsByCategoryId(Long categoryId);
232238
}

src/main/java/com/ongil/backend/global/common/exception/ErrorCode.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@ public enum ErrorCode {
3333
PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "상품을 찾을 수 없습니다.", "PRODUCT-001"),
3434
PRODUCT_OPTION_NOT_FOUND(HttpStatus.NOT_FOUND, "선택한 옵션(색상/사이즈)을 찾을 수 없습니다.", "PRODUCT-002"),
3535
OUT_OF_STOCK(HttpStatus.BAD_REQUEST, "재고가 부족합니다.", "PRODUCT-003"),
36+
INVALID_STOCK(HttpStatus.BAD_REQUEST, "재고는 0 이상이어야 합니다.", "PRODUCT-004"),
3637

3738
// BRAND
3839
BRAND_NOT_FOUND(HttpStatus.NOT_FOUND, "브랜드를 찾을 수 없습니다.", "BRAND-001"),
40+
CANNOT_DELETE_BRAND_WITH_PRODUCTS(HttpStatus.BAD_REQUEST, "해당 브랜드를 사용하는 상품이 있어 삭제할 수 없습니다.", "BRAND-002"),
3941

4042
// CATEGORY
4143
CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "카테고리를 찾을 수 없습니다.", "CATEGORY-001"),
4244
INVALID_CATEGORY(HttpStatus.BAD_REQUEST, "상품은 하위 카테고리에만 등록할 수 있습니다.", "CATEGORY-002"),
45+
CANNOT_DELETE_CATEGORY_WITH_PRODUCTS(HttpStatus.BAD_REQUEST, "해당 카테고리를 사용하는 상품이 있어 삭제할 수 없습니다.", "CATEGORY-003"),
46+
CANNOT_DELETE_CATEGORY_WITH_SUBCATEGORIES(HttpStatus.BAD_REQUEST, "하위 카테고리가 있어 삭제할 수 없습니다.", "CATEGORY-004"),
47+
CATEGORY_SELF_REFERENCE(HttpStatus.BAD_REQUEST, "카테고리의 상위 카테고리로 자기 자신을 설정할 수 없습니다.", "CATEGORY-005"),
4348

4449
// WISHLIST
4550
WISHLIST_NOT_FOUND(HttpStatus.NOT_FOUND, "찜을 찾을 수 없습니다.", "WISHLIST-001"),

0 commit comments

Comments
 (0)