inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

자바 ORM 표준 JPA 프로그래밍 - 기본편

프로젝션(SELECT)

특정 칼럼에 대한 조회시 TypedQuery 사용에 대하여

해결된 질문

355

강우석

작성한 질문수 22

2

안녕하세요, 예전에 수업들은 이후 한창 현업 중에 잘못 사용한 Case 이지만, 왜 그럴까 디버깅도 좀 해보고 생각을 많이 해보다가 해결을 못하고 질문을 남기게 되었습니다. 그나마 관련이 있는 부분을 찾아서 남기게 되었습니다.

 

앱에는 Foo 라는 엔티티가 있고, 우선 다음과 같은 JPQL 쿼리를 발생시켰습니다.

String jpql = 
    "SELECT f.id FROM Foo f " + 
    "WHERE f.name = :name " + 
    "ORDER BY f.createdAt DESC" 

// id 라는 특정 칼럼 조회이므로 Type 을 명시하지 않고 Query 를 써야 하는게 맞지만, 잘못 사용해서 TypedQuery 를 사용함

List<Foo> fooList = em.createQuery(jpql, Foo.class).setParameter("name", name).getResultList();

 

일단 예외가 나지 않고 fooList 라는 List 가 반환된다는 것도 신기했습니다. 정확한 이해를 하지는 못했지만 디버깅 해본 결과, CgLibAopProxy 클래스들을 타는걸보면, Lazy 로딩과 유사하게 CustomFoo extends Foo 와 같은 상속객체를 만들어서, id 만 채워준 후 List 에 넣어서 반환해주기 때문에 문법적으로 fooList 를 만들 때 오류가 발생하는게 아니구나 판단했습니다. (아닐 수도 있습니다.. 제 생각)

 

이후 fooList.get(index) 를 시도하면서 바로 Casting 예외가 발생하였습니다. (Long 객체를 Foo 객체로 Casting 할 수 없다)

 

신기해서 IntelliJ 에서 디버깅 하면서 확인했을 때, fooList 라는 List 안에 Foo 객체들이 들어 있지 않고, Long 객체들이 들어 있는 모습을 확인했습니다. 이거는 좀 매우 이상한게, 어쨌든 Foo 라는 Type 이 들어가게끔 선언된 List 에 어떻게 Long 객체들만 들어가 있을 수 있는지가 이해가 되지 않았습니다.

 

createQuery() 함수와 getResultList() 함수간 복잡한 제네릭 관계들을 이해하기가 어려워서 이 부분을 이해하지 못한 것 같기도 합니다.

 

물론 TypedQuery 를 사용하지 않고 Query 를 사용하거나 DTO 를 통해 매핑해주는 것이 맞지만, 지금 상황 기준으로

 

  1. 어떠한 이유로 List 형성 중 에러가 나지 않은 것인지

  2. 또 어떻게 List<Foo> 안에 인텔리제이 디버깅 기준으로 Long 객체들이 들어가 있을 수 있는 것인지 궁금합니다




    _-----------------------------
    추가로 나중에 subQuery 안에서도 동일하게 사용이 되었는데, 이런 경우는 쿼리 결과를 반환해주지 않고 그냥 쿼리문을 만들 때만 사용되어서 그런지 아무 에러가 발생하지 않았습니다.

 

SELECT SUM(~~.~~) from Bar bar 
WHERE bar.foo.id in (
    SELECT f.id FROM Foo f 
    ~~
)

 

java jpa

답변 2

3

인프런 AI 인턴

안녕하세요, 인프런 AI 인턴입니다.

강우석님의 질문에 대해 답변드립니다.

JPA에서 TypedQuery는 결과 타입을 명시적으로 지정할 때 사용되며, 결과 타입을 잘못 지정했음에도 불구하고 별다른 예외 없이 List를 반환하는 이유는 JPA 구현체가 실제 쿼리 수행 시점이 실행 시점(Runtime)이기 때문입니다. 즉, JPQL 쿼리를 준비하는 단계에서는 실제 쿼리가 수행되지 않고, 쿼리 메서드(getResultList())를 호출할 때 쿼리가 실행됩니다. 따라서 createQuery(jpql, Foo.class)라고 작성했을 때 컴파일 시점에서는 문제가 없다고 판단하게 됩니다.

그러나 실제 쿼리 결과는 f.id 즉, Long 타입이며 이는 Foo 타입이 아닙니다. 그래서 쿼리의 결과 리스트 내부에는 Long 객체들이 담기게 됩니다. 그런데, 해당 리스트를 List<Foo>로 받으려고 시도하였기 때문에, 리스트의 제네릭 타입이 일치하지 않는 문제가 발생합니다.

IntelliJ의 디버깅을 통해 리스트 내부에 Long 객체들만 있다는 것을 확인한 것은, 실제 쿼리의 결과가 Long 타입임에도 불구하고 타입에 맞지 않게 List<Foo>로 받아 처리하려 했기 때문입니다. 즉, JPA 구현체가 타입 체크를 런타임에 하기 때문에 이런 현상이 발생한 것으로 보입니다.

이러한 문제를 피하기 위해서는 반환되는 컬럼의 타입에 맞는 적절한 반환 타입으로 TypedQuery를 생성해야 합니다. f.id의 경우 Long 타입이므로 TypedQuery<Long>을 사용해야 합니다.

  1. List 형성 중에 에러가 나지 않은 것은, List<Foo>로 선언하는 것이 실제 내부 구현과 독립적으로 이루어졌기 때문입니다. 구체적인 실행 및 타입 확인은 런타임에 수행되며, 따라서 제네릭 타입이 잘못되었다 하더라도 컴파일 시점에서는 에러가 나지 않습니다.

  2. List 안에 Long 객체들이 들어간 것은 컴파일러가 실제 타입을 강제할 수 있는 방법이 없기 때문입니다. 자바의 제네릭 타입은 타입 소거(Type Erasure)가 적용되어 런타임에는 모든 제네릭 타입 정보가 소거되어 원시 타입으로 처리되기 때문입니다. 결과적으로는 List에 잘못된 타입의 객체가 들어갈 위험이 있으며, 이를 사용하는 측에서 올바르게 처리하지 않으면 ClassCastException을 던지게 됩니다.

즉, 올바르게 타입을 처리하지 않고 TypedQuery<Foo>로 쿼리를 생성한 상황에서 예외 없이 통과된 것은 컴파일 시점과 런타임 타입 체크의 타이밍 차이 때문이며, 실제 쿼리 결과 타입 (Long)과 선언한 타입 (Foo)이 일치하지 않아서 발생한 문제입니다.

항상 쿼리를 작성할 때 반환되는 컬럼과 결과 타입이 일치하도록 주의하여, 올바른 타입의 TypedQuery를 사용해야 합니다.

1

김영한

안녕하세요. 강우석님

이 부분은 자바의 제네릭에 대한 이해가 필요한데요.

AI 인턴의 답변을 참고해주세요.

감사합니다.

inheritance startegy 선택시 고려사항

0

11

0

Entity 동등성 비교

0

14

1

실무 조언 관련 질문입니다.

0

41

1

H2데이터베이스 파일 생성

0

52

2

서브쿼리 강의에서 ALL 예시 관련 질문드립니다.

0

49

2

수정또는 삭제시 영속성 엔티티에 값이 무조건 있어야 하나요?

0

48

1

JPQL 메소드와 락

0

53

1

Delivery @OneToOne

0

57

1

17강 4~5분대 테이블 값 조회가 안됩니다.

0

89

2

UnsupportedOperationException 발생

0

82

3

H2 Database 연결이 안됩니다.

0

89

2

연관관계 매핑 질문드립니다.

0

82

2

h2데이터베이스 실행오류

0

105

2

persistence.xml

0

103

2

양방향 연관관계에서 연관관계의 주인(mappedBy)을 왜 꼭 정해야 하나요?

0

77

1

영속성 컨텍스트

0

62

1

JPA 프록시

0

90

1

Native Query와 MyBatis

0

63

1

영속성 컨텍스트는 어떤 메모리에 저장되는건가요?

0

84

1

임베디드 타입 예시 코드 관련 질문

0

112

3

명시적 조인에서 별칭을 주면 왜 객체에 접근할 수 있나요

0

91

3

인텔리제이 패키지 커서 단축키 질문

0

106

2

혹시 현재는 ID 데이터 타입이 String이면 안되나요?

0

137

1

양방향 연관관계 시 연관관계 주인을 설정하는 이유

0

68

1