오류 내용

프로젝트 실행시 다음과 같은 에러가 발생했다.

이는 한 필드에 하나의 값이 할당되어야 하는데, 해당 타입이 여러개라 의존성 주입이 제대로 되지 않아 발생한 에러이다.

 

 

발생 원인

에러가 난 부분을 보면,

 

현재 Controller 내에서 Service 빈이 제대로 주입되지 않는다.

바보 같이 구현체(TempQueryServiceImpl)를 가지고 와야하는데 인터페이스(TempQueryService)를 적어버렸다 .. ^^

TempQueryService를 상속한 TempQueryServiceImpl를 적지 않고 TempQueryService를 적으니, 빨간 줄이 그어졌는데 인텔리제이에서 @Qualifier 어노테이션을 적용해서 수정하라고 띄워준다.

 

해결방법

나처럼 오탈자로 간단하게 해결하는 경우 외에, 인터페이스 및 구현체로 인하여 제대로 된 의존성 주입이 되지 않을 때의 해결법이다. 대표적으로 @Qualifier, @Primary 어노테이션을 통해 클래스를 지정할 수 있다. 

 

@Qualifier 어노테이션의 경우 인터페이스를 상속한 클래스가 여러 개일 때 해당 어노테이션에 클래스명을 기입하여 멤버변수에 의존성을 주입해준다.

@Qualifier("클래스명")
private MyInterface myInterface;

 

 

혹은 생성자 인자에 의존성을 주입할 수도 있다.

private MyInterface myInterface;

@Autowired
public 생성자(@Qualifier("클래스명") MyInterface myInterface) {
    this.myInterface = myInterface;
}

 

정리내용

오타로 발생한 이슈,, 였지만 인터페이스 & 구현체를 바탕으로 MVC 모델 구축하는 것이 아직 어색하다. 많이 활용하면서 객체지향의 참맛을 익혀야쥐

 

build.gradle 파일에 queryDsl을 세팅하기 위해 의존성을 추가 후 compileQuerydsl을 실행했더니 다음과 같은 에러가 발생했다.

 

영한님의 Querydsl 강의를 다시 찾아보니 강의 자료에 "스프링 부트 2.6 이상, Querydsl 5.0 지원 방법"에 대한 내용이 추가 되어있었다.

 

buildscript {
   ext {
      queryDslVersion = "5.0.0"
   }
}

plugins {
   id 'org.springframework.boot' version '2.6.8'
   id 'io.spring.dependency-management' version '1.0.11.RELEASE'
   // querydsl 추가
   id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
   id 'java'
}

group = 'com.wangtak'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
}

repositories {
   mavenCentral()
}

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
   implementation 'org.springframework.boot:spring-boot-starter-web'

   //querydsl 추가
   implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
   annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"

   compileOnly 'org.projectlombok:lombok'
   developmentOnly 'org.springframework.boot:spring-boot-devtools'
   annotationProcessor 'org.projectlombok:lombok'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
   useJUnitPlatform()
}

// querydsl 세팅 시작
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
   jpa = true
   querydslSourcesDir = querydslDir
}
sourceSets {
   main.java.srcDir querydslDir
}
configurations {
   querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
   options.annotationProcessorPath = configurations.querydsl
}
// querydsl 세팅 끝

위 처럼 querydsl의 버전 명시와 querydsl-jpa, querydsl-apt를 추가해준다.

오류 내용

Quizeloper 좋아요 기능을 만드는 중, 제목과 같은 에러가 발생했다.

엔티티매니저가 없어 삭제 요청을 수행하지 못한다니..!

 

에러가 발생한 Service 코드는 다음과 같다.

 

public void postQuizLikes(Long userId, Long quizId){
        User user = userRepository.findByIdAndStatus(userId, ACTIVE).orElseThrow(() -> new BaseException(BaseResponseStatus.USER_NOT_FOUND));
        Quiz quiz = quizRepository.findByIdAndStatus(quizId, ACTIVE).orElseThrow(() -> new BaseException(BaseResponseStatus.QUIZ_NOT_FOUND));
        if (quizLikeRepository.existsIdByUserIdAndQuizId(user.getId(), quizId)) {
            quizLikeRepository.deleteByUserIdAndQuizId(userId, quizId);
        }
        else {
            QuizLike quizLike = QuizLike.builder().user(user).quiz(quiz).build();
            quizLikeRepository.save(quizLike);
        }

    }

유저와 퀴즈를 조회한 후, 좋아요 DB에 해당 유저와 퀴즈가 존재하는지에 따라 좋아요, 또는 좋아요 취소를 할 수 있다.

quizLikeRepository.deleteByUserIdAndQuizId(userId, quizId); 부분에서 에러가 발생했다.

 

 

발생 원인

확인해보니 삭제 메서드 수행시 삭제가 제대로 되지 않은 것이다.

 

DeleteBy_Id 동작 방식

1. EntityManager opened
2. SELECT 쿼리 생성
3. EntityManager closed
4. DELETE 쿼리 예외 발생

 

기본적으로 JPA는 트랜잭션을 기반으로 작동하게 되어 있는데, 트랜잭션 단위에 따라 1차 캐시 영역에 있는 객체들이 db에 flush되어 영속화 되기 때문이다.

 

영속작업을 하는 persist() 메소드에 객체가 들어갔으나 가능한 트랜잭션이 존재하지 않았기에 본 에러가 발생한 것이다.

 

 

해결 방법

서비스 혹은 클래스 위에 @Transactional을 선언해둠으로써 해결할 수 있었다.

 

그럼 지금까지의 Service 메서드들 중 @Transactional을 선언하지 않고도 어떻게 외부 호출이 가능했을까?

바로 JpaRepository의 구현체인 SimpleJpaRepository에서 이미 메서드에 @Transactional을 붙여 놓아서이다.

 

그래서 JpaRepository에 미리 선언된 메서드를 호출할 경우에, 서비스 클래스에서 @Transactional을 붙이지 않아도 문제가 없었던 것이다. 따라서 레포지토리 클래스에 추가로 정의한 메서드에 @Transactional을 붙여줘도 문제를 해결할 수 있다.

 

정리 내용

  • @Transactional 어노테이션을 통해 스프링에서 트랜잭션 관리를 단순화할 수 있다.
    • 데이터 일관성과 에러 처리가 간편하게 관리된다.
  • 트랜잭션은 어노테이션 기반 AOP를 통해 구현되어 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체가 생성된다.
  • 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우 트랜잭션을 시작하고 Commit이나 Rollback을 수행한다.
  • CheckedException이나 예외가 없을 때 Commit, UncheckedException이 발생하면 Rollback된다.
    • 디버깅을 통해 Commit, Rollback 과정을 바탕으로 예외 처리를 진행해야겠다.
  • JPA에서 단일 작업에 대해서는 @Transactional을 직접 선언할 필요가 없다.
    • 여러 작업을 하나의 단위로 묶어 Commit이나 Rollback 처리가 필요할 때 직접 선언한다!

 

오류 내용

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의 관계이다.

Quiz 엔티티는 퀴즈 테이블, QuizUnitList 엔티티는 퀴즈 문제 유형들을 담는 엔티티이다.

테이블 연관관계 매핑시 @OneToMany 어노테이션에 mappedBy 속성을 추가하였다.

 

QuizUnitList 엔티티
Quiz 엔티티

 

QuizUnitList 엔티티에서 Quiz 엔티티를 참조할 때 작성한 필드명은 quiz인데 mappedBy 속성에 잘못하고 quiz_id로 잘못입력해 오류가 발생하였다.

 

 

해결 방법

수정 후 Quiz 엔티티

quiz_id에서 quiz로 고쳐주니 오류없이 잘 실행되었다! 

 

 

 

정리 내용

  • DB에서는 FK를 통해 맺는 연관관계를 객체로 표현할 때는 연관관계 주인과 mappedBy로 나타내야 한다.
  • 다대일 관계에서 다가 되는 쪽이 연관관계 주인이 된다.
  • 연관관계 주인이 되는 부분은 Join할 컬럼을 나타내야 한다.
    • @ManyToOne 어노테이션을 통해 연관관계를 나타내고 @JoinColumn(name = "quiz_id")을 통해 어떤 컬럼(필드명)과 조인을 하는지 나타내면 된다.
  • mappedBy는 연관관계를 맺으며 주인이 되는 객체의 필드명을 적어준다.

MultipleBagFetchException 이 발생했는데, 

entity 설계하면서 한 테이블 내에 @OnetoMany 및 fetch 타입을 2개 썼더니 발생한 에러였다.

보통 이 에러는 해당 어노테이션을 2개 이상 썼을 때 발생하는 에러이다.

 

관련하여 N+1 문제가 발생한다는 ..

 

지금 만들고 있는 프로젝트에서 @OnetoMany가 쓰이는 곳에 fetch = FetchType.EAGERfetch = FetchType.Lazy로 변경해주니 해결되었다.

 

추후에 매핑관계와 fetch 타입, 이어 N+1 문제에 관하여 블로그 글을 추가로 작성해야겠다 !!

참고한 글 : https://eclipse4j.tistory.com/215

 

윈도우에 설치시 발생하는 오류(wslregisterdistribution failed with error: 0x80370102) 해결 | 코드잇

저도 동일한 오류가 발생했었는데, 다음과 같은 방법으로 해결하였습니다. 1. Linux 커널 업데이트 패키지 설치 https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi (error: 0x800701bc wsl 2? ?? ?? ?? ??

www.codeit.kr

처음에 이 에러를 보고 구글링하면서 위 게시물을 발견하고

해당 게시물의 해결방법을 그대로 수행했다.

 

1. Linux 커널 업데이트 패키지 설치

https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi

(error: 0x800701bc wsl 2? ?? ?? ?? ????? ?????. ??? ??? https://aka.ms/wsl2kernel? ??????. 해결)

 

2. Windows 기능 켜기/끄기 에서

  • Linux용 WIndows 하위 시스템
  • Windows 하이퍼바이저 플랫폼
  • 가상 머신 플랫폼

체크여부 확인하고 안되어있을시 클릭하여 체크 후 재부팅

 

3. 개발자 설정에 들어가서 개발자 모드 활성화 시키고 재부팅

(wslregisterdistribution failed with error: 0x80370102 please enable the virtual machine platform windows feature and ensure virtualization is enabled in the bios. 해결)

 

 

 

근데 이 세개를 다 해도 똑같이 에러가 떴다...

 

[WSL] 가상 컴퓨터 플랫폼 Windows 기능을 사용하도록 설정하고 BIOS에서 가상화가 사용하도록 설정

개요 Android 개발환경을 설정하며 의도치 않게 WSL2의 구동에 영향을 미치는 환경설정을 변경하였습니다. C:\Windows\system32>wsl 가상 컴퓨터 플랫폼 Windows 기능을 사용하도록 설정하고 BIOS에서 가상화

rottk.tistory.com

이 글을 참고하자면,

관리자 권한으로 cmd를 실행하고,

bcdedit

와 같은 명령어를 입력한다.

 

그러면 이와 같이 hypervisorlaunchtype이 off 상태이다.

 

bcdedit /set hypervisorlaunchtype auto

hypervisorlaunchtype을 auto로 바꾸기 위해 다음과 같은 명령어를 입력해주고

다시 재부팅한다.

 

그러고 ubuntu 다시 실행해주면..!!!

에러문은 없어지고 정상적으로 이름을 입력하라는 메세지가 뜬다.

성공!

JPA로 findBy~를 사용하던 중 발생한 에러이다.

query did not return a unique result : 5

이는 repository에서 조회한 결과는 5개인데 이를 class로 받아 담을 수 없기에 발생한다.

List<Class>로 받게 되면 오류 해결 할 수 있다!

해당 repository 구문으로 데이터를 조회해야하는데 제목과 같은 에러가 발생했다.

구글링 결과 파라미터의 타입이 맞지 않아 생기는 오류라고..!

entity에서 선언된 타입과 repository에서 입력한 타입과 맞지 않아 에러가 발생되는 케이스이다.

둘 타입을 맞게 설정해주면 에러 해결 완료!

repository단에 동적 쿼리가 담긴 메소드를 작성한 후 잘 돌아가는지 테스트하려고 하는데 위와 같은 에러가 떴다.

 

알고보니 내가 동적으로 쿼리를 날리는 경우라면 NativeQuery를 True로 설정해야하는 것이다.

@Query(value = "select * from member_status where user_profile_idx = :userProfileIdx limit 1", nativeQuery = True)

다음과 같이 NativeQuery를 True로 선언해주니 바로 해결했다!

+ Recent posts