여행 여정을 기록과 관리하는 서비스
Backend | Backend | Backend | Backend |
---|---|---|---|
자현 | 민우 | 유림 | 동민 |
로그인/회원가입/찜 | 장바구니 조회&삭제/예약 | 장바구니 추가/숙소목록 상세 조회 | 숙소 전체 조회/검색 조회 |
⏲️ 개발 기간
- 1차 : 2023.11.10 ~ 2023.11.16
- 2차(리팩토링) : 2023.11.04 ~ 2023.11.15
🔗 배포 사이트
🔨 구현 환경
- Java 17
- Spring Boot 3.1.5
- Mysql 8.0, H2, Redis
- Docker
- Intellij
- gradle
- test - Junit
- github actions & aws code deploy
에러 내용 및 해결
1 - 1. 원인
Infinite recursion (StackOverflowError)
(through reference chain: com.aroom.domain.room.model.Room["accommodation"]
->com.aroom.domain.accommodation.model.Accommodation["roomList"]
->org.hibernate.collection.spi.PersistentBag[0]
->com.aroom.domain.room.model.Room["accommodation"]
->com.aroom.domain.accommodation.model.Accommodation["roomList"]-
현재 양방향 연관관계에 놓여진 Accommodation과 Room에서 무한순환참조가 발생했다.
1 - 2. 해결
@OneToMany
@manytoone
로 인해 순환참조 원인@JsonManagedReference
&@JsonBackReference
추가
@JsonManagedReference
@OneToMany(mappedBy = "accommodation", fetch = FetchType.LAZY)
private List<Room> roomList = new ArrayList<>();
@JsonManagedReference
: 부모Entity
→ 자식Entity
- 정상적으로 직렬화를 수행
@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "accommodation_id")
private Accommodation accommodation;
@JsonBackReference
: 자식Entity
→ 부모Entity
-
직렬화 수행 x
⇒ 무한 순환 참조 해결
-
2 - 1. 발생 과정
public RoomCartResponseDTO postRoomCart(Long member_id, Long room_id){
Room room = roomRepository.findById(room_id).get();
Cart cart = cartRepository.findByMemberId(member_id).get();
RoomCart roomCart = roomCartRepository.save(new RoomCart(cart,room));
cart.postRoomCarts(roomCart);
return new RoomCartResponseDTO(cart);
}
@OneToMany(mappedBy = "cart", fetch = FetchType.LAZY)
private List<RoomCart> roomCartList = new ArrayList<>();
public void postRoomCarts(RoomCart roomCart){
roomCartList.add(roomCart);
}
객실을 장바구니에 담을 때 RoomCart를 생성하여 Cart의 List roomCartList에 post 시도
2 - 2. 원인
Type definition error: [simple type, class com.aroom.domain.roomCart.dto.response.RoomCartResponseDTO]
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.aroom.domain.roomCart.dto.response.RoomCartResponseDTO]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:489) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103) ~[spring-web-6.0.13.jar:6.0.13]
at
caused by: com.fasterxml.jackson.databind.exc.invaliddefinitionexception:
no serializer found for class com.aroom.domain.roomcart.dto.response.roomcartresponsedto
and no properties discovered to create beanserializer
(to avoid exception, disable serializationfeature.fail_on_empty_beans)
(through reference chain: com.aroom.global.response.apiresponse["data"])
- Jackson 라이브러리가
RoomCartResponseDTO
&RoomCartInfoDTO
를 직렬화할 때 문제가 발생 - Jackson은 기본적으로 클래스를 직렬화할 때 해당 클래스에 대한 직렬화 메소드를 찾아야 하는데, 여기서는 해당 메소드를 찾지 못했다고 나온다.
- 또한, Jackson은 직렬화 하는 과정에서 기본으로 접근 제한자가 public이거나, getter/setter를 이용하기 때문에 인스턴스 필드를 private등으로 선언시, json으로 변환 과정에서 에러가 발생한다.
2 - 3. 해결
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class RoomCartResponseDTO {
private long cart_id;
private List<RoomCartInfoDTO> roomCartList;
public RoomCartResponseDTO(Cart cart) {
this.cart_id = cart.getId();
List<RoomCartInfoDTO> roomCartInfoDTOList = new ArrayList<>();
for(RoomCart roomCart : cart.getRoomCartList()){
RoomCartInfoDTO roomCartInfoDTO = new RoomCartInfoDTO(roomCart);
roomCartInfoDTOList.add(roomCartInfoDTO);
}
System.out.println(roomCartInfoDTOList.size()); // 정확히 나옴
this.roomCartList = roomCartInfoDTOList;
}
}
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class RoomCartInfoDTO {
private long room_id;
private long cart_id;
@Builder
public RoomCartInfoDTO(long room_id, long cart_id) {
this.room_id = room_id;
this.cart_id = cart_id;
}
public RoomCartInfoDTO(RoomCart roomCart) {
this.room_id = roomCart.getRoom().getId();
this.cart_id = roomCart.getCart().getId();
}
}
-
JsonAutoDetect 설정 제거
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
- private 필드에 접근 가능하여 json으로 변환 가능하다.
-
Fetch.Type을 EAGER로 바꾸는 것은 보안의 문제가 있으므로 고려하지 않았습니다.
-
또한, Entity Class에 @JsonProperty 또는 @JsonAutoDetect를 직접 선언할 수 있으나, Entity를 최대한 변경하지 않고자 DTO에 선언했습니다.
3 - 1. 원인
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.querydsl.jpa.impl.JPAQueryFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1824)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1383)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:910)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788)
... 108 more
해당 설정은 전역적으로 빈을 컨테이너에 생성하는 것이기 때문에 Entity
와 Respository
빈만 생성하는 @DataJpaTest의
경우에는 JpaQueryFactory
빈을 생성하지 못하는 문제가 생기게 됩니다.
3 - 2. 해결
@Configuration
@EnableJpaAuditing
@EnableJpaRepositories(basePackages = "com.aroom")
public class JpaConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory queryFactory() {
return new JPAQueryFactory(entityManager);
}
}
해당 문제를 해결하기 위해서는 실제 JPAQueryFactory
를 사용하는 곳에서만 해당 빈을 생성하면 됩니다.
양유림
- 2주동안 짧은 시간 내에 FE개발자와 협업하는 것이 생각보다 어려웠다.
- 하지만, FE개발자와 소통하면서, API를 발전시켜나가는 과정에서 많은 걸 깨달을 수 있었다.
- 또한, 에러를 겪고 해결한 방안을 정리하면서 다시 한번 성장할 수 있었다.
- KPT기간에 CICD를 통해 무중단 배포를 도입하니, 빠르게 API를 배포할 수 있어서 시간을 절약할 수 있었다.
권민우
- 시간이 부족해 많은 고민과 토론을 진행하지 못해 아쉬웠다.
- 프로젝트 생성 부터 배포, 어플리케이션 구동까지 모든 프로세스를 경험하게 되어 도움이 됐다.
- 프로젝트를 진행하며 부족한 부분을 알게 되었고 보충 할 수 있는 시간도 주어져 좋았다.
차동민
- 기존 스프린트에서 해결하지 못한 문제를 해결할 수 있는 기회가 주어져서 좋았다.
- QueryDsl이라는 배우면서 적용하는것이 기간이 짧아 힘들었는데, KPT 기간을 활용하여 데이터베이스를 깊게 공부할 수 있어서 좋았다.
- 그리고 더욱 깔끔한 코드를 작성하기 위해 시퀀스 다이어그램을 이용하는 등, 리팩토링의 중요성에 대해 깨닫게 되었다.
- 문제해결에 대한 깊은 고민과 접근방법에 대해서 깨닫게 되었다.
구자현
- 빠른 테스트서버 배포를 위해 어떻게 프로세스를 짜야할 지 정립할 수 있는 시간이 된 것 같아 좋았다.
- 깊은 객체 다이어그램을 어떻게 분리할 수 있을지에 대한 도메인적 관점에 대한 공부를 할 수 있어서 좋았다.