입사 후 공부하며 기술 스텍을 쌓던 내게 던져진 첫 실무 과제.
stack : Springboot 2.3.6 RELEASE, gradle, JPA, mysql ...
저번시간에 이어 이번에는 Service 계층의 로직을 포스팅할 것이다.
그 전에 DTO 클래스를 구현하자 😊
@Getter
@Setter
@NoArgsConstructor
public class CategoryDTO {
private Long categoryId;
private String branch;
private String code;
private String name;
private String parentCategoryName;
private Integer level;
private Map<String, CategoryDTO> children;
public CategoryDTO (Category entity) {
this.categoryId = entity.getId();
this.branch = entity.getBranch();
this.code = entity.getCode();
this.name = entity.getName();
this.level = entity.getLevel();
if(entity.getParentCategory() == null) {
this.parentCategoryName = "대분류";
} else {
this.parentCategoryName = entity.getParentCategory().getName();
}
this.children = entity.getSubCategory() == null ? null :
entity.getSubCategory().stream().collect(Collectors.toMap(
Category::getName(), CategoryDTO::new
));
}
public Category toEntity () {
return Category.builder()
.branch(branch)
.code(code)
.level(level)
.name(name)
.build();
}
}
{
"ROOT": {
"categoryId": 1,
"branch": "coupang",
"code": "ROOT",
"name": "ROOT",
"parentCategoryName": "대분류",
"level": 0,
"children": {
"1": {
"categoryId": 2,
"branch": "coupang",
"code": "1",
"name": "clothes",
"parentCategoryName": "ROOT",
"level": 1,
"children": {}
}
}
}
}
builder를 사용하여 명시적으로 매개변수를 넣어줄 수 있게끔 만들어준다.
드디어, service 로직 포스팅을 한다. 😃
우선 saveCategory 메소드를 살펴보자.
리팩토링 과정도 쓸까 하다가 쓰면서 주섬주섬 생각나는 것들을 포스팅해보겠다.
@Service
@RequiredArgsConstructor
@Transactional
public class CategoryService {
private final CategoryRepository categoryRepository;
public Long saveCategory (CategoryDTO categoryDTO) {
Category category = categoryDTO.toEntity();
//대분류 등록
if (categoryDTO.getParentCategoryName() == null) {
//JPA 사용하여 DB에서 branch와 name의 중복값을 검사. (대분류에서만 가능)
if (categoryRepository.existsByBranchAndName(categoryDTO.getBranch(), categoryDTO.getName())) {
throw new RuntimeException("branch와 name이 같을 수 없습니다. ");
}
//orElse로 refactor
Category rootCategory = categoryRepository.findByBranchAndName(categoryDTO.getBranch(),"ROOT")
.orElseGet( () ->
Category.builder()
.name("ROOT")
.code("ROOT")
.branch(categoryDTO.getBranch())
.level(0)
.build()
);
if (!categoryRepository.existsByBranchAndName(categoryDTO.getBranch(), "ROOT")) {
categoryRepository.save(rootCategory);
}
category.setParentCategory(rootCategory);
category.setLevel(1);
//중, 소분류 등록
} else {
String parentCategoryName = categoryDTO.getParentCategoryName();
Category parentCategory = categoryRepository.findByBranchAndName(categoryDTO.getBranch(), parentCategoryName)
.orElseThrow(() -> new IllegalArgumentException("부모 카테고리 없음 예외"));
category.setLevel(parentCategory.getLevel() + 1);
category.setParentCategory(parentCategory);
parentCategory.getSubCategory().add(category);
}
//category.setLive(true);
return categoryRepository.save(category).getId();
}
데이터의 비교는 DB단에서 최대한 거른 다음에 가져오는 것이 빠르다.
for (Category temp : categories()) {
if (temp.getBranch().equals(category.getBranch()) && temp.getName().equals(category.getName())){
throw new RuntimeException("branch와 name이 같은 대분류는 있을 수 없습니다.");
}
}
```
원래는 위와 같은 로직을 짜려고 했는데, 이렇게 할경우 데이터를 왕창 다가져와서 비교하므로 느리다.
최대한 DB에서 처리할 수 있는 것은 처리해서 뱉는 것이 좋다. 그렇기 때문에 CategoryRepository 인터페이스에서 boolean타입의 메소드를 생성한 것이다. (1편 참조 링크 click)