이번에는 저번시간에 이어 CategoryService를 계속 살펴보겠습니다.
저번 시간에는 read 메서드를 알아봤는데요, 이어서 create를 알아보겠습니다.
CategoryService
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class CategoryService {
private final CategoryRepository categoryRepository;
public List<CategoryDto> readAll() {
List<Category> categories = categoryRepository.findAllOrderByParentIdAscNullsFirstCategoryIdAsc();
return CategoryDto.toDtoList(categories);
}
@Transactional
public void create(CategoryCreateRequest req) {
categoryRepository.save(CategoryCreateRequest.toEntity(req, categoryRepository));
}
@Transactional
public void delete(Long id) {
if(notExistsCategory(id)) throw new CategoryNotFoundException();
categoryRepository.deleteById(id);
}
private boolean notExistsCategory(Long id) {
return !categoryRepository.existsById(id);
}
}
create
@Transactional
public void create(CategoryCreateRequest req) {
categoryRepository.save(CategoryCreateRequest.toEntity(req, categoryRepository));
}
create 메서드에서는 CategoryCreateRequest 타입의 입력값을 받아 Category Entity로 변환후에 저장합니다.
CategoryCreateRequest
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CategoryCreateRequest {
@NotBlank(message = "카테고리 명을 입력해주세요.")
@Size(min = 2, max = 30, message = "카테고리 명의 길이는 2글자에서 30글자 입니다.")
private String name;
private Long parentId;
public static Category toEntity(CategoryCreateRequest req, CategoryRepository categoryRepository) {
return new Category(req.getName(),
Optional.ofNullable(req.getParentId())
.map(id -> categoryRepository.findById(id).orElseThrow(CategoryNotFoundException::new))
.orElse(null));
}
}
먼저 CategoryCreateReuest 타입의 입력값과 categoryRepository를 가지고 Entity 변환하는 메서드를 가진 CategoryCreateRequest클래스입니다. toEntity 메서드에서는 Category를 성생하여 반환해주고 반환갑승ㄴcategoryRepository.save()를 통해 저장됩니다.
new Category() 부분을 살펴보면 Optional.ofNullable 함수를 사용하고 있습니다. 간단하게 설명하자면 parent_id가 null 이 아니라면 parent category를 가져오고 null이라면 null을 가져옵니다.
(ofNullable에 대한 자세한 내용은 다음 글을 참고해주세요.)
https://coding-kim.tistory.com/41
[Java] ofNullable()에 대해서 알아보자
Java 8부터 제공되는 Optional 클래스는 null-safe한 코드 작성을 도와주는 유용한 클래스입니다. Optional 클래스는 객체를 감싸서 그 객체가 null인지 아닌지 여부를 판단할 수 있습니다. ofNullable() 메소
coding-kim.tistory.com
OfNullable 테스트
ofNullable에 대한 이해를 돕고자 간단한 테스트를 작성해 보았습니다.
public class OptionalTest {
@Test
public void 변수가Null인경우(){
//given
String input = null;
//when
Optional<String> optionalInput = Optional.ofNullable(input);
String result = optionalInput.orElse("input_null");
//then
Assertions.assertThat(result).isEqualTo("input_null");
}
@Test
public void 변수가Null이아닌인경우(){
//given
String input = "input";
//when
Optional<String> optionalInput = Optional.ofNullable(input);
String result = optionalInput.orElse("input_null");
//then
Assertions.assertThat(result).isEqualTo(input);
}
}
Exception
Category를 만들면서 발생하는 Exception들을 정리 하겠습니다.
public class CategoryNotFoundException extends RuntimeException {
}
public class CannotConvertNestedStructureException extends RuntimeException {
public CannotConvertNestedStructureException(String message) {
super(message);
}
}
ExceptionAdvice
@ExceptionHandler(CategoryNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Response categoryNotFoundException() {
return Response.failure(-1010, "존재하지 않는 카테고리입니다.");
}
@ExceptionHandler(CannotConvertNestedStructureException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Response cannotConvertNestedStructureException(CannotConvertNestedStructureException e) {
log.info("e = {}", e.getMessage());
return Response.failure(-1011, "중첩 구조 변환에 실패하였습니다.");
}
Category Service 테스트
@ExtendWith(MockitoExtension.class)
class CategoryServiceTest {
@InjectMocks
CategoryService categoryService;
@Mock
CategoryRepository categoryRepository;
@Test
void readAllTest() {
// given
given(categoryRepository.findAllOrderByParentIdAscNullsFirstCategoryIdAsc())
.willReturn(
List.of(CategoryFactory.createCategoryWithName("name1"),
CategoryFactory.createCategoryWithName("name2")
)
);
// when
List<CategoryDto> result = categoryService.readAll();
// then
assertThat(result.size()).isEqualTo(2);
assertThat(result.get(0).getName()).isEqualTo("name1");
assertThat(result.get(1).getName()).isEqualTo("name2");
}
@Test
void createTest() {
// given
CategoryCreateRequest req = createCategoryCreateRequest();
// when
categoryService.create(req);
// then
verify(categoryRepository).save(any());
}
@Test
void deleteTest() {
// given
given(categoryRepository.existsById(anyLong())).willReturn(true);
// when
categoryService.delete(1L);
// then
verify(categoryRepository).deleteById(anyLong());
}
@Test
void deleteExceptionByCategoryNotFoundTest() {
// given
given(categoryRepository.existsById(anyLong())).willReturn(false);
// when, then
assertThatThrownBy(() -> categoryService.delete(1L)).isInstanceOf(CategoryNotFoundException.class);
}
}
public class CategoryCreateRequestFactory {
public static CategoryCreateRequest createCategoryCreateRequest() {
return new CategoryCreateRequest("category", null);
}
public static CategoryCreateRequest createCategoryCreateRequestWithName(String name) {
return new CategoryCreateRequest(name, null);
}
}
'spring > 게시판 api' 카테고리의 다른 글
Spring boot 게시판 API 서버 제작 (16) - 중간 정리 & 카테고리 API (0) | 2023.02.27 |
---|---|
Spring boot 게시판 API 서버 제작 (15) - 게시판 - 카테고리 - 웹 계층 구현 (0) | 2023.02.26 |
Spring boot 게시판 API 서버 제작 (13) - 게시판 - 카테고리 (0) | 2023.02.24 |
Spring boot 게시판 API 서버 제작 (12) - 로그인 - api 문서 만들기 (1) | 2023.02.23 |
Spring boot 게시판 API 서버 제작 (11) - 로그인 - Token 코드 리팩토링 (0) | 2023.02.23 |