들어가며
TIL 업로드 하는걸 깜빡해서 15일에 올리는 14일 TIL..!!
오늘의 TIL도 과제를 하면서 겪었던 에러를 트러블 슈팅해보고자 한다!
트러블 슈팅
문제 - DataIntegrityViolationException 에러
could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
not null인 테이블에 null을 집어 넣으려고 해서 나타나는 에러였다.
에러로그에서 바로 힌트를 얻을 수 있는데, 메인 에러 명은 데이터 무결성 위반 예외 이고, 뒤쪽에 적힌 에러명은 제약조건 위반 예외이다.
SQL을 공부할 때, 제약 조건을 걸어주는 이유가 무결성을 지키기 위함이었다고 배웠었다.
즉, 내가 뭔가 제약조건에 반하는 짓을 했다는 말인데 바로 생각나는 부분은 User Entity와의 매핑관계로 생성된 FK였다.
하지만 이 부분은 따로 해줄 수 있는게 없었고, 지금 에러를 뱉어낸 부분은 updatePost API를 호출할 때였으므로, 다른 곳에는 문제가 없다고 판단했다.
그럼 내가 위반할 법한 제약 조건이 뭐가 있을까?
바로 image_url 컬럼에 붙여준 Not null 제약조건이다. api를 호출할 때 body에 file을 넣지 않고 보내준게 원인이었다.
post의 imageUrl 기획은 Not null이 맞으므로 file을 넣지않고 보내는 경우만 조심하도록 하고 이 부분에 에러로그가 그대로 나오는 경우를 방지하기 위해 CustomErrorCode에 다음과 같은 상황을 추가해주었다.
NULL_FILE(HttpStatus.INTERNAL_SERVER_ERROR, "file is required")
그리고, 해당 에러가 service에 들어왔을 때 예외를 던져주도록 다음 코드를 적절한 위치에 붙여주었다.
throw new RestApiException(CustomErrorCode.NULL_FILE);
문제 - MultipleBagFetchException 에러
post를 조회하는 api를 호출할 때, 다음과 같은 에러가 나타났다.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.homework.homework_week2.user.domain.User.roles, com.homework.homework_week2.user.domain.User.comments]
첫번째 시도 - comment의 EAGER 설정 삭제하기
구글링을 해보니 EAGER 전략이 중첩되면 나타나는 오류라고 한다.
에러로그의 마지막 부분에 쓰여있는 문제가 있는 클래스를 확인해보았더니
Post.java
@OrderBy(value = "createdAt DESC")
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<Comment> comments;
User.java
@ElementCollection(fetch = FetchType.EAGER)
@Builder.Default
private List<String> roles = new ArrayList<>();
이 두 부분이 문제였다. 아마 Comment나 Likes Entity에서 저 Post와 User Entity를 참조하고 있기 때문에 해당 Entity를 불러오면서 생기는 오류인 것 같다.
roles의 필드에는 EAGER를 지울 수 없어서, comment에 있는 속성을 지워줌으로써 해결!
…
했다고 생각했으나 또 같은 에러가 터졌다. 다시 구글링!!!
두번째 시도 - Service 클래스에 @Transactional 어노테이션 붙여주기
comment에 있는 EAGER전략을 지워주고나면 이번에는 LazyInitializationException 에러가 떴다.
결국 EAGER 전략의 문제가 아니라는 뜻인가? 파워 구글링을 해보았다.
구글링 끝에 갓 김영한 강사님에게서 해답을 얻을 수 있었다!!
https://www.inflearn.com/questions/33949
김영한 강사님의 댓글
org.hibernate.LazyInitializationException 이 오류는 member를 조회까지는 성공했는데, member.get MemberSocialProfiles() 를 호출해서 사용할 때 영속성 컨텍스트가 종료되어 버려서, 지연 로딩을 할 수 없어서 발생하는 오류 입니다. JPA에서 지연로딩을 하려면 항상 영속성 컨텍스트가 있어야 하거든요.
아직도 지연로딩과 즉시로딩에 개념이 어려워서 정확한 이유를 파악하지는 못했지만
찬찬히 코드를 살펴보면
나는 stackoverflow를 해결하기 위해 Post Entity를 PostResponseDto로 매핑하는 과정을 거친 후 PostResponseDto를 반환해주는 방식을 사용하고 있었는데, 이 과정에서 Comment Entity를 조회하다가 무언가 문제가 터진 것 같다.
그럼 해결방법은 comment를 호출할 때 영속성 컨텍스트가 종료되지 않도록 하면 된다.
@Transactional
public class PostService {
...
}
결국 문제가 되고있는 PostService에 @Transactional 어노테이션을 붙여줌으로써 해결했다.
하지만 디버깅을 돌리면 controller에서 service로 넘어오는 순간 프록시관련 문제가 나타나는걸 봐선 아직 완전한 해결법이라고 볼 수 는 없을 것 같다.
JPA에 대해 더 공부해가 필요할 것 같다!
마치며
두번째 문제를 거의 3시간이나 붙잡고 있었다. JPA를 문제없이 다룰 수 있게 되려면 얼마나 걸릴까...?
개발의 길은 멀고도 험한 것 같다는 생각이 드는 밤이다.
'Study > TIL' 카테고리의 다른 글
[TIL] 06/16 항해99 39일차 - 여러가지 정리 (0) | 2022.06.16 |
---|---|
[TIL] 06/15 항해99 38일차 - application.yml 파일 분리하기, AWS S3 적용하기 (0) | 2022.06.16 |
[TIL] 06/13 항해99 36일차 - Timestamp format 변경, 트러블 슈팅 (1) | 2022.06.13 |
[TIL] 06/11 항해99 34일차 - Lombok으로 생성자주입, 트러블 슈팅 (0) | 2022.06.11 |
[TIL] 06/10 항해99 33일차 - 프록시, 즉시 로딩 / 지연 로딩 (0) | 2022.06.11 |