토이 프로젝트 스터디 #11
- 스터디 진행 날짜 : 6/21
 
- 스터디 작업 날짜 : 6/18 ~ 6/21
 
토이 프로젝트 진행 사항
- 카테고리 관련 코드 작성
RDB에서 계층형 구조를 적용하기 위해 Adjacency List 방식 사용 
 
Redis를 활용한 Cache 적용 
내용
Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
    @Query("SELECT c FROM Category c LEFT JOIN c.parent p ORDER BY p.id ASC NULLS FIRST, c.id ASC")
    List<Category> findAllOrderByParentIdAsc();
    Optional<Category> findByName(String name);
}
findAllOrderByParentIdAsc()
- 부모 카테고리가 먼저 조회될 수 있도록 
@Query로 직접 SQL 입력 
NULL FIRST : 자식이 없는 부모 카테고리 먼저 조회 
 
Service
@Service
@Transactional
@RequiredArgsConstructor
public class CategoryService {
    private final CategoryRepository categoryRepository;
    @Transactional(readOnly = true)
    @Cacheable(value = "category", key="'categories'")
    public List<NestedCategoryResponse> findAll() {
        List<Category> categories = categoryRepository.findAllOrderByParentIdAsc();
        return CategoryResponse.fromEntity(categories);
    }
    @CacheEvict(value = "category", key="'categories'", allEntries = true)
    public void save(CreateCategoryRequest request) {
        categoryRepository.save(
                CreateCategoryRequest.toEntity(
                        request.getName(),
                        Optional.ofNullable(request.getParentId())
                                .map(id -> categoryRepository.findById(id).orElseThrow(CategoryNotFoundException::new))
                                .orElse(null))
        );
    }
    public void delete(Long id) {
        if (!categoryRepository.existsById(id)) {
            throw new CategoryNotFoundException();
        }
        categoryRepository.deleteById(id);
    }
}
- 카테고리의 경우 계층형 구조를 표현하기 위해 
Adjacency List 사용
- 가장 간단하지만 성능이 제일 좋지 않은 방식
 
- 카테고리의 경우 비교적 변경되지 않으며, 조회의 경우 
Redis를 활용해 Cache로 처리하면 된다고 판단 
 
findAll()
@Cacheable을 통해 Redis에 저장된 값이 있으면 해당 값을 조회 
 
save()
@CacheEvict를 통해 카테고리의 변경 사항을 Cache에 반영하기 위해 기존 값 삭제  
 
Controller
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/categories")
public class CategoryController {
    private final CategoryService categoryService;
    @GetMapping()
    public ResponseEntity findAllCategories() {
        return new ResponseEntity(FindCategoryResponseConverter.convert(categoryService.findAll()), HttpStatus.OK);
    }
    @PostMapping()
    public ResponseEntity addCategory(@Validated @RequestBody CreateCategoryRequest request) {
        categoryService.save(request);
        return new ResponseEntity(HttpStatus.CREATED);
    }
    @DeleteMapping("/{id}")
    public ResponseEntity deleteCategory(@PathVariable Long id) {
        categoryService.delete(id);
        return new ResponseEntity(HttpStatus.OK);
    }
}
Cache 동작 확인

