JAVA Spring boot) 소비기록앱 소비일기 서버(Backend) 개발일지 4 - API 서버 개발_일기 목록
- 작성일 : 2022.08.27
-
작성자 : 황시아
- 팀원 : 박승지, 오성은, 황시아
- GitHub Repository : https://github.com/miro7923/soil
개발환경
- MacBook Air (M1, 2020)
- OpenJDK 11
- IntelliJ IDEA Community Edition
- Spring Boot 2.7.3
- MySQL Workbench 8.0.28
기간
- 2022.8.15 ~
주제
- 오늘의 소비에 대한 기억과 감정을 기록으로 남기고 나중에 되돌아보는 시간이 있었으면 좋겠다는 생각에 진행하게 된 프로젝트이다.
- 새로 산 물건의 사진을 찍고 그에 대한 감상을 함께 글로 남길 수 있는 기능을 메인으로 제작할 것이다.
진행상황
-
모든 진행은 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 강의를 기반으로 했다.
-
회원 한 명이 작성한 전체 일기 목록을 리턴하는 API 서버를 만들었다.
DiaryRepository
Repository
에서 회원의 아이디로 일기 목록을 조회하는 메서드를 작성했다.
@Repository
@RequiredArgsConstructor
public class DiaryRepository {
private final EntityManager em; // 의존성 자동 주입
public List<Diary> findAll(Long id) {
return em.createQuery("select d from Diary d where d.member.id = :member_id", Diary.class)
.setParameter("member_id", id)
.getResultList();
}
}
- 이 메서드를 만들며 요청에 대한 응답이 제대로 오지 않는 문제가 발생했었다.
- 해결과정은 에러해결 Log) QueryException could not resolve property column of entity 에 작성했다.
DiaryService
- 이제
Service
에서는 아까 만든Repository
의 메서드를 호출해 주면 된다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class DiaryService {
private final DiaryRepository diaryRepository;
/**
* 일기 전체 조회
* @return List<Diary>
*/
public List<Diary> findDiaries(Long id) {
return diaryRepository.findAll(id);
}
}
- 파라미터로 조회하고자 하는 회원의 아이디를 넣어준다.
DiaryApiController
HTTP Request
와URL
매핑을 해 준 다음 메서드를 작성했다.
일기 목록 리턴 메서드 v1
@RestController
@RequiredArgsConstructor
public class DiaryApiController {
private final DiaryService diaryService;
/**
* 회원 한 명의 일기 목록을 응답
* @param id
* @return id에 해당하는 회원의 일기 전체 목록
*/
@GetMapping("/diaries/{id}")
public List<Diary> diaries(@PathVariable Long id) {
return diaryService.findDiaries(id);
}
}
- 이제 완벽하다고 생각했는데 막상 실행하니까 또 동작이 제대로 되지 않는 것이었다.
- 왜냐면 객체를 직접 반환하다 보니
Diary
객체에 있는Member
와Category
객체 필드도 채워줘야 하는데 저것들은@ManyToOne(fetch = FetchType.LAZY
로 설정했기 때문에 일기 목록만 조회하는 시점에서는 프록시 객체로만 있는 상태지 실제 객체가 존재하는 것은 아니다. 그래서 사실상 빈 상태이다 보니 필드가 채워지지 않아
No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
-
이런 에러가 나는 것이었다.
- 이걸 해결하려면
@ManyToOne
어노테이션마다@JsonIgnore
를 붙여주던가application.yml
에서
spring:
jackson:
serialization:
FAIL_ON_EMPTY_BEANS: false
- 이런 식으로 빈 필드에 대한 예외처리를 해 주는 설정값을 추가해 주어야 했다. 하지만 일기 목록을 조회할 때 회원과 카테고리의 모든 정보가 필요한 것이 아니기 때문에 빈 필드에 대한 예외처리를 해 주는 것은 사실상 불필요한 작업이라고 할 수 있다. 회원 한 명의 일기 목록을 조회하는 것이기 때문에 요청하는 쪽에서 회원 정보는 이미 가지고 있는 상태이고, 카테고리에서 필요한 정보는 카테고리 이름 정도이다. 그래서 한마디로 하면 좋긴 하겠지만 굳이 할 필요가 없는 작업인 것이다.
- 그래서 또 강의를 참고하여… 객체의 직접 리턴이 아닌 별도의
DTO
를 만들어 리턴하는 방식으로 바꿨다.
일기 목록 리턴 메서드 v2
JSON
데이터로 만들 클래스
@Data
@AllArgsConstructor
public class Result<T> {
private int count;
private T diaryList;
}
- 각 일기 하나가 담고 있을 정보를 담은 클래스
@Data
@AllArgsConstructor
public class DiaryDto {
private String category;
private String title;
private String content;
private LocalDateTime date;
private String photo;
private int price;
}
@RestController
@RequiredArgsConstructor
public class DiaryApiController {
private final DiaryService diaryService;
private final MemberService memberService;
private final CategoryService categoryService;
/**
* 회원 한 명의 일기 목록을 응답
* @param id
* @return id에 해당하는 회원의 일기 전체 목록
*/
@GetMapping("/diaries/{id}")
public Result diaries(@PathVariable Long id) {
List<Diary> findDiaries = diaryService.findDiaries(id);
Member findMember = memberService.findMember(id);
Category findCategory = categoryService.findCategory(1L);
List<DiaryDto> collect = findDiaries.stream()
.map(d -> new DiaryDto(
d.getCategory().getName(),
d.getTitle(),
d.getContent(),
d.getDate(),
d.getPhoto(),
d.getPrice()))
.collect(Collectors.toList());
return new Result(collect);
}
}
- 화면에서 회원의 전체 일기 목록을 보여주는 데 필요한 파라미터만 넘기기 위해 별도의
DTO
클래스를 생성한 뒤Result
객체에 담아 리턴했다.
{
"count": 5,
"diaryList": [
{
"category": "food",
"title": "first buy",
"content": "fiirst diary",
"date": "2022-08-25T23:00:44.068405",
"photo": null,
"price": 1000
},
{
"category": "food",
"title": "second buy",
"content": "second diary",
"date": "2022-08-25T23:02:41.910958",
"photo": null,
"price": 2000
},
{
"category": "food",
"title": "third buy",
"content": "third diary",
"date": "2022-08-25T23:03:12.428172",
"photo": null,
"price": 3000
},
{
"category": "food",
"title": "fourth buy",
"content": "fourth diary",
"date": "2022-08-25T23:03:31.481156",
"photo": null,
"price": 4000
},
{
"category": "food",
"title": "fifth buy",
"content": "fifth diary",
"date": "2022-08-25T23:04:35.310611",
"photo": null,
"price": 5000
}
]
}
- 그리하여 응답 받은 데이터! 추후 추가로 필요해진 파라미터를 추가할 수 있게 확장에 열린
API
가 되었다.Result
클래스의 필드값만 수정하면 되기 때문에 해당API
에서 응답해야 하는 데이터에 변경이 생겼을 경우 수정해야 하는 부분을 최소화 할 수 있다.
정리
- 강의를 한 번 본 뒤 나의 프로젝트에 맞게 변형해 작성한 코드였는데 처음엔 에러가 참 많이 났다. 맞게 한 거 같은데 왜 나는 안 되는 걸까…
- 문제는 강의를 한 번 본 것으로 어느정도 이해하고 코드를 작성할 수 있다고 착각한 것이었다. 그래서 빨리 만들자는 마음을 버리고 강의를 다시 한 번 복습하며 내 코드의 문제점을 깨닫고 처음에 작성했던 코드는 왜 동작하지 않았는지 원인을 파악하는 시간을 가졌다. 그리고
JPA
의 동작을 좀 더 이해하는 데에 많은 도움이 되었다. 이제 같은 실수는 하지 않을 거 같다!