com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 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) (through reference chain
이 에러는 Jackson 라이브러리가 객체를 JSON으로 직렬화하는 과정에서 발생하였다.
직렬화란 Object를 연속된 String 데이터나 연속된 Bytes 데이터로 바꾸는 것을 의미한다.
에러 메시지에서 나오는 내용을 해석하자면, Jackson이 org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor 클래스를 직렬화할 수 있는 방법을 찾지 못했다는 것이다.
발생 원인
문제 원인 코드 QuizService
public GetPagedQuizRes getQuizList(int size, int page) {
List<Quiz> quizList= quizRepository.findAllByStatus(ACTIVE);
List<Long> quizLikes = quizLikeRepository.findAllByUserAndStatus(1L, ACTIVE);
if(page > quizList.size()/size){
throw new BaseException(PAGE_COUNT_OVER);
}
PageRequest pageRequest = PageRequest.of(page, size);
int start = (int) pageRequest.getOffset();
int end = Math.min((start + pageRequest.getPageSize()), quizList.size());
Page<Quiz> pagingQuiz = new PageImpl<>(quizList.subList(start, end), pageRequest, quizList.size());
int total = quizList.size();
List<GetQuizRes> quizResList = pagingQuiz.getContent().stream()
.map(quiz -> new GetQuizRes(quiz.getTitle(), quiz.getType(), quiz.getStackUnit(), quiz.getQuizUnit(),
checkUserLikesQuiz(quizLikes, quiz)))
.collect(Collectors.toList());
return new GetPagedQuizRes(quizResList, total, page);
}
이 문제는 일반적으로 Hibernate의 지연 로딩(Lazy Loading) 기능 때문에 발생한다. Hibernate는 프록시(proxy)를 사용하여 지연 로딩을 구현하는데, 이로 인해 직렬화할 때 문제가 발생할 수 있다.
나의 경우 해당 코드에서 에러가 발생했는데, 두번째 line에서 quiz.getQuizUnit() 부분에서 에러가 났다.
Quiz 엔티티와 관계된 QuizUnit(ManyToOne)이 FetchType.LAZY로 설정되어 있다.
Jackson으로 Quiz 엔티티를 Serialize를 할때, LAZY 설정으로 비어있는 객체(QuizUnit)를 Serialize 하려고 해서 발생되는 문제이다.
해결 방법
1. DTO로 바꾸어 사용할 데이터만 반환해 사용
DTO에 엔티티를 직접 담기보다 사용할 데이터만 조회할 수 있도록 한다.
디버깅을 돌려봤을 때 QuizUnit의 프록시 객체를 조회하는 것을 확인할 수 있다.
중간객체를 설정하여 중간객체의 구체적인 값을 불러오고 DTO에 담아주는 식으로 로직을 수정하니 해결되었다.
2. 오류가 발생하는 컬럼에 @JsonIgnore 설정
해당 필드를 Json으로 변경시 제외된다.
Json의 응답결과에 @JsonIgnore이 선언되어 있는 객체가 빠지게 되어 오류를 해결할 수 있다.
그러나 선언한 엔티티의 값도 포함하고 싶은 경우 Json 응답 결과가 빠져버리게 되어 알 수 없다.
이런 경우 서비스 내 응답 결과로 해당 엔티티가 필요 없는 경우의 확장성까지 생각해 선언하는 것이 좋아보인다.
3. LAZY를 EAGER로 변경
하지만 이 방법은 N+1 문제를 발생시킬 수 있어 좋은 방법은 아닌 거 같다.
4. application 파일에 spring.jackson.serialization.fail-on-empty-beans=false 설정
오류만 안나오도록 하는거라 어떤 문제가 있을지는 모른다.
5. 각 Entity쪽에 @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 설정
정리 내용
가급적 LAZY 로딩 전략을 사용하고, 엔티티를 엮어서 함께 사용해야한다면 JPQL의 fetch join을 통해 한방 쿼리로 가져와 사용하자.
스프링부트는 @ResponseBody를 선언할 때 Object를 json으로 변환하기 위해 Jackson 라이브러리를 사용한다.
Jackson 순환 참조 에러는 @OneToMany, @ManyToOne에서 반복 참조로 인하여 직렬화를 이용해 json 형태로 객체를 변환시킬 때 발생한다.
Quizeloper 엔티티 추가 수정사항이 있어 수정하고 다시 빌드했더니 다음과 같은 에러가 발생했다.
오류 내용
Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Collection 'com.cs.quizeloper.quiz.entity.Quiz.quizUnitList' is 'mappedBy' a property named 'quiz_id' which does not exist in the target entity 'com.cs.quizeloper.quiz.entity.QuizUnitList'
프로젝트 실행할 때 자동으로 해당 class의 이름을 가진 DB 테이블을 생성하기 위해 JPA(Hibernate)를 사용했다.
이 에러는 entityManagerFactory가 빈 생성 중에 오류가 발생한 것으로, 어떤 컬렉션 필드가 문제가 있는 것으로 보인다. 해당 컬렉션은 'mappedBy' 속성이 사용되었는데, 이 속성의 값인 'id'라는 이름의 속성이 연관된 엔티티 클래스에 존재하지 않는다는 뜻이다.
발생 원인
문제의 엔티티는 Quiz, QuizUnit으로 Quiz-[One-Many]-QuizUnitList의 관계이다.
(wslregisterdistribution failed with error: 0x80370102 please enable the virtual machine platform windows feature and ensure virtualization is enabled in the bios. 해결)
근데 이 세개를 다 해도 똑같이 에러가 떴다...
이 글을 참고하자면,
관리자 권한으로 cmd를 실행하고,
bcdedit
와 같은 명령어를 입력한다.
그러면 이와 같이 hypervisorlaunchtype이 off 상태이다.
bcdedit /set hypervisorlaunchtype auto
hypervisorlaunchtype을 auto로 바꾸기 위해 다음과 같은 명령어를 입력해주고