3월 ~ 6월 초 기간동안 spring 입문 / 핵심원리, spring mvc, jpa, jpa 활용편 1/2 등등을 수강하고
드디어 개발한 나의 작고 귀여운 프로젝트🥹
원래는 화면을 반으로 나눴을 때 왼쪽 부분은 포모도로 타이머, 오른쪽 부분은 투두리스트가 보이도록
개발하려고 했으나, 프론트엔드를 책임질 나의 chatgpt4o 님께서 말귀를 잘 못알아먹으시는 바람에,,,
투두 기능에 집중하여 개발했다.
사용한 기술 스택
백엔드: Spring boot, JPA
DB: MySQL
프론트엔드: javascript, html, css
RestAPI 방식으로 개발했다.
개발한 기능
- Todo 생성 - 이름, 카테고리, 마감기한, 진행상태 지정 가능
- 진행상태는 "완료", "진행중", "대기" 가 있음 (enum 클래스로 개발)
- Category와 Todo 는 다대일 관계로 개발
- Todo 수정 기능
- Todo 삭제 기능
- Todo 필터 검색 기능
#API 명세서
Http Method | API path | 기능 |
GET | /api/todo | 투두 리스트 홈 화면 |
POST | /api/todo | 투두 리스트 생성 |
POST | /api/todo/edit/{todo_id} | 투두 리스트 수정 |
DELETE | /api/todo/delete/{todo_id} | 투두 리스트 삭제 |
# 이슈 / 고민 / 배운 것
- Category:Todo = N:1
투두 엔티티에 List<Category> categories 를 단순히 멤버로 넣을 생각도 했으나
하면서 배운다는 목적이 강했던 토이 플젝인 만큼, 따로 Category 엔티티를 만들어서
JPA 다대일 관계로 설정했다.
투두를 생성할 때, 일단 DTO에 바인딩되는 것들은 이름, 마감기한, 진행상태 세가지로 개발했다.
카테고리는 DTO로 투두 객체를 생성한 다음, 따로 양방향 연관관계를 넣어주는 메소드로 값들을 넣어주었다.
- 진행상태: N/A
분명 http 요청 / 응답 모두 "status":"xxx" json 형식으로 잘 받고/보냈는데,
홈화면에진행상태: N/A 로 뜨는 이슈 발생.
백 로직에는 아무 문제가 없는 것 같아서 gpt가 짠 Js 코드를 보니
바인딩 되는 변수 이름이 status가 아니라 taskStatus로 되어있었다. 바로 status 로 바로 바꿔주니 해결😂
gpt랑 개발할 때는 변수 이름 하나하나 꼼꼼히 살펴보고 복붙하자 하하...
- 진행상태: ONGOING (영어)
js 를 수정하니 잘 뜨긴 하는데 enum 필드 이름 그대로 "ONGOING", 혹은 "COMPLETED" 등과 같이 떴다.
한글로 뜨면 더 좋겠다싶어 찾은 방법은 TaskStatus 이넘 클래스에
@JsonFormat(shape = JsonFormat.Shape.OBJECT)를 붙이고,
private final String name;
private TaskStatus(String name){this.name = name;} 추가하여 name에 한글 이름을 저장하도록 했다.
- @JsonFormat(shape = JsonFormat.Shape.OBJECT)
객체를 JSON으로 직렬화할 때, 해당 객체의 속성들을 별도의 필드로 포함시킨다.
기본적으로 enum이 JSON으로 직렬화될 때 이름(상수명)으로 직렬화되므로, 추가한
name 과 같은 속성을 포함하기 위해서 이 어노테이션을 붙였다.
- em.remove 정상 동작 X
// ----------------수정 전--------------------//
//TodoService 내
List<Category> categories = todo.getCategories();
categories.forEach(categoryRepository::deleteByE); // 문제 발생
dto.getCategories().forEach(todo::addCategory);
//CategoryRepository 내
public void deleteByE(Category category) {
em.remove(category);
}
// ---------------수정 후--------------------//
List<Category> categories = todo.getCategories();
categories.forEach(m -> {
Category findCategory = categoryRepository.findOne(m.getId());
categoryRepository.deleteByE(findCategory);
});
수정 전에는 부끄럽게도 todo 객체에서 categories를 가져온 뒤, 각각 em.remove를 호출했다.
단순히 todo 객체에서 category를 조회해왔다고 해서 영속성 컨텍스트에 올려지는 것이 아닌데, 저땐 왜저렇게 코드를 짰는지... ㅠㅠ
해결방법
메소드 deleteByE로 em.remove를 호출하기 전, findOne을 호출하여 미리 영속성 컨텍스트에 올려놓는다.
이후 em.remove를 호출하면, 삭제하려는 엔티티가 영속성 컨텍스트에 존재하기 때문에 정상적으로 삭제된다.
혹은
//Todo 도메인에서 category 연관관계에 orphanRemoval = true 설정
@OneToMany(mappedBy = "todo", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Category> categories = new ArrayList<>();
// TodoService 내
todo.getCategories().clear();
dto.getCategories().forEach(todo::addCategory);
Todo 에서 categories에 고아 객체가 자동으로 삭제되도록 하는 orphanRemoval 옵션을 true로 설정하여,
todo.getCategories().clear(); 로 리스트에서 제거된 category는 자동으로 삭제되도록 만들 수도 있다.
# 마치며
JPA 활용편을 수강하면서 이것보다 더 복잡한 상품 주문 프로젝트를 개발할 때
이정도면 혼자 개발할 때도 척척 할 수 있겠다! 싶었는데 막상 혼자 "무"에서 시작하려니 막막한 느낌을 받았다.
그래도 한번 조그마한 것이라도 완료해보고 나니 자신감이 어느정도 생긴 것 같다.
나에게 더 보완이 필요한 점들은
- RestAPI, 클라이언트 사이드 렌더링 방식으로 개발 더 해보기
- JPA 연관관계를 자유자재로 다룰 수 있도록 연습
spring 강의를 들을 때, thymeleaf를 사용했었어서 SSR 방식으로 개발한
경험이 없었다. 그래도 이번에 혼자 어떻게든 해본 것 같아서 뿌듯하다:)😀
클라우드 엔지니어에서 백엔드 개발자로 늦게 노선을 바꾸고 마음고생도 많이 했었는데, 뭐 앞으로도
맘고생 많이 할 것 같지만 그래도 열심히 해야지 어쩌겠어 그.열.해.어 ~ 🤓 화이팅 👏👏
'개발' 카테고리의 다른 글
빌더 패턴과 @Builder: 객체 생성의 번거로움을 해결하자! (0) | 2024.10.09 |
---|---|
RESTful API가 뭔가요? (8) | 2024.10.09 |
워터폴 방법론 (0) | 2024.09.27 |
도서 추천 & 독서 일지 프로젝트(2) - ERD 설계 (0) | 2024.08.05 |
도서 추천 & 독서 일지 프로젝트 (1) - 계획서 (0) | 2024.06.17 |
update() 메소드는 repository에 둬야할까, service에 둬야할까? (0) | 2024.06.14 |