Hibernate Query.list ()를 List <Type>으로 캐스팅하는“적절한”방법은 무엇입니까?


84

저는 Hibernate의 초보자이고 특정 필터와 일치하는 개체 목록을 반환하는 간단한 메서드를 작성하고 있습니다. List<Foo>자연스러운 반환 유형처럼 보였습니다.

내가 무엇을하든, 나는 추악한 .NET을 사용하지 않는 한 컴파일러를 행복하게 만들 수 없습니다 @SuppressWarnings.

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;

public class Foo {

    public Session acquireSession() {
        // All DB opening, connection etc. removed,
        // since the problem is in compilation, not at runtime.
        return null;
    }

    @SuppressWarnings("unchecked") /* <----- */

    public List<Foo> activeObjects() {
        Session s = acquireSession();
        Query   q = s.createQuery("from foo where active");
        return (List<Foo>) q.list();
    }
}

나는 그것을 없애고 싶다SuppressWarnings . 하지만 그렇게하면 경고를받습니다

Warning: Unchecked cast from List to List<Foo>

(무시할 수 있지만 처음에는 가져 오지 않으려 고합니다.) .list()반환 유형 을 준수하기 위해 제네릭을 제거 하면 경고가 표시됩니다.

Warning: List is a raw type. References to generic type List<E>
should be parameterized.

나는 그 발견 org.hibernate.mapping 하지 를 선언 List; 그러나 그것은 완전히 다른 유형입니다- 원시 유형으로를 Query반환합니다 java.util.List. 최근의 Hibernate (4.0.x)가 매개 변수화 된 유형을 구현하지 않는 것이 이상하다는 것을 알게 되었기 때문에 대신 내가 뭔가 잘못한 것 같다고 생각합니다.

객체 목록에 대한 Cast Hibernate 결과 와 매우 유사 하지만 여기에는 "하드"오류가 없습니다 (시스템은 Foo 유형을 알고 있으며 SQLQuery가 아니라 직접적인 쿼리를 사용하고 있습니다). 그래서 기쁨이 없습니다.

나는 또한 유망 해 보였기 때문에 Hibernate Class Cast Exception 을 보았지만 실제로 는 아무것도 얻지 못한다 는 것을 깨달았습니다 Exception... 내 문제는 경고의 문제입니다.

jboss.org에 대한 문서, Hibernate 매뉴얼 및 여러 튜토리얼은 그 주제를 그렇게 자세하게 다루지 않는 것 같습니다 (또는 올바른 위치에서 검색하지 않았습니까?). 세부 사항을 입력 할 때, 그들은 즉석 캐스팅을 사용합니다. 그리고 이것은 공식 jboss.org 사이트에 없었던 튜토리얼에 대한 것이므로 약간 조심합니다.

일단 컴파일 된 코드는 명백한 문제 없이 실행됩니다 ... 내가 아는 ... 아직; 결과는 예상되는 것입니다.

그래서 :이 일을 제대로하고 있습니까? 나는 명백한 것을 놓치고 있습니까? "공식"또는 "권장" 방법이 있습니까?

답변:


101

짧은 대답 @SuppressWarnings이 올바른 방법입니다.

긴 대답, Hibernate는 메서드 List에서 원시 를 반환합니다 . 여기를Query.list 참조 하십시오 . 이것은 Hibernate의 버그 나 해결할 수있는 것이 아닙니다 . 쿼리에 의해 반환 된 유형 은 컴파일 타임에 알려지지 않습니다 .

따라서 당신이 쓸 때

final List<MyObject> list = query.list();

에서 List로 안전하지 않은 캐스트를 수행하고 List<MyObject>있습니다. 이것은 피할 수 없습니다.

무엇이든 포함 List 할 수 있으므로 캐스트를 안전하게 수행 할 수있는 방법은 없습니다 .

오류를 없애는 유일한 방법은

final List<MyObject> list = new LinkedList<>();
for(final Object o : query.list()) {
    list.add((MyObject)o);
}

4
더 나은 답변이 나오기를 바라면서 귀하의 답변 만 찬성 할 예정이었습니다. - 대신에 나는이 문제가 모두 브루스 에켈 (Bruce Eckel) (자바에서 생각)와 로버트 세지에 의해 "추악한 캐스트"이라 발견 세지. 또한 stackoverflow.com/questions/509076/… 을 찾았습니다 . 한숨.
LSerni

6
사용하는 스타일이 마음에 final
Pavel

9
최대 절전 모드 Class<?>에서 유형이있는 인수를 추가 list()하면 문제가 해결 될 수 있습니다. 이렇게 못생긴 API를 사용하는 것은 부끄러운 일입니다.
빈 왕

@BinWang 그러면 안전하지 않은 캐스트가 다른 곳에서 발생하지만 문제가 해결되지 않습니다. 말할 필요도없이 HQL API는 수년 동안 효과적으로 사용되지 않습니다 . JPA에는 Criteria Query API 라는 유형 안전 쿼리 API가 있습니다.
보리스 스파이더

2
@PeteyPabPro 원시 형을 피해야한다는 데 동의하지만 쿼리 결과를 List<Object>. 결과는 예상 된 유형으로 캐스트 되어야하며 쿼리가 올바른 결과를 반환하는지 확인하기 위해 단위 테스트 를 추가해야합니다. 쿼리가 " 코드 뒷부분에서 "오류가 발생하는 것은 용납 할 수 없습니다 . 귀하의 예는 21 세기에 혐오스러운 코딩 관행에 반대하는 주장입니다. 그것은 나는 건의 할 것입니다 결코 을 가지고 허용 List<Object>.
보리스 스파이더

26

해결 방법은 대신 TypedQuery를 사용하는 것입니다. EntityManager에서 쿼리를 만들 때 대신 다음과 같이 호출합니다.

TypedQuery<[YourClass]> query = entityManager.createQuery("[your sql]", [YourClass].class);
List<[YourClass]> list = query.getResultList(); //no type warning

이것은 또한 명명 된 쿼리, 네이티브 명명 된 쿼리 등에 대해 동일하게 작동합니다. 해당 메서드는 바닐라 쿼리를 반환하는 것과 동일한 이름을 갖습니다. 반환 유형을 알 때마다 Query 대신 이것을 사용하십시오.


1
참고로 이것은 인라인으로 만드는 순수 네이티브 쿼리에는 작동하지 않습니다. 반환 된 쿼리는 typedquery가 아닌 쿼리 일뿐입니다. :(
Taugenichts

이제 오류 메시지가 사라지고 결과 목록은 Object 객체 대신 실제 객체를 사용합니다. 훌륭한 대답입니다!
Johannes

이것은 최대 절전 모드 5.0에서 릴리스 된 것으로 보입니다. 4.3.11에서는 볼 수 없지만 다음 기사에서는 5.3을 참조합니다. wiki.openbravo.com/wiki/… 또한 2014 년 1 월 stackoverflow 게시물을 참조합니다 : stackoverflow.com/a/21354639/854342 .
Curtis Yallop

그것은 hibernate-jpa-2.0-api의 일부입니다. 현재 최대 절전 모드 4.3에서 사용하고 있으므로 최대 절전 모드 4.3에서 사용할 수 있습니다.
Taugenichts

6

다음과 같은 해결 방법으로 컴파일러 경고를 피할 수 있습니다.

List<?> resultRaw = query.list();
List<MyObj> result = new ArrayList<MyObj>(resultRaw.size());
for (Object o : resultRaw) {
    result.add((MyObj) o);
}

그러나이 코드에는 몇 가지 문제가 있습니다.

  • 불필요한 ArrayList 생성
  • 쿼리에서 반환 된 모든 요소에 대한 불필요한 루프
  • 더 긴 코드.

그리고 그 차이는 외형 일 뿐이므로 그러한 해결 방법을 사용하는 것은-제 생각에는-무의미합니다.

당신은 이러한 경고와 함께 살거나 억제해야합니다.


1
나는 무의미함에 동의합니다. 내 곡식에 어긋나더라도 억제하겠습니다. 감사합니다. +1.
LSerni

2
> 당신은 이러한 경고와 함께 살거나 억압해야합니다. 그것은 잘못 supress 경고에 항상 더 나은, 또는 당신은 unsupressed 잘못된 경고의 스팸 바로 경고를 놓칠 수 있습니다
SpongeBobFan

6

귀하의 질문에 답하기위한 "올바른 방법"은 없습니다. 이제 당신을 괴롭히는 경고 일 뿐이라면 확산을 피하는 가장 좋은 방법은 Query.list()메서드를 DAO로 래핑하는 것입니다.

public class MyDAO {

    @SuppressWarnings("unchecked")
    public static <T> List<T> list(Query q){
        return q.list();
    }
}

이렇게하면 @SuppressWarnings("unchecked")한 번만 사용할 수 있습니다 .


Stack Overflow에 오신 것을 환영합니다 ! 어쨌든,이 걸릴 것을 잊지 마세요 투어
Sнаđошƒаӽ

3

나를 위해 일하는 유일한 방법은 반복자를 사용하는 것입니다.

Iterator iterator= query.list().iterator();
Destination dest;
ArrayList<Destination> destinations= new ArrayList<>();
Iterator iterator= query.list().iterator();
    while(iterator.hasNext()){
        Object[] tuple= (Object[]) iterator.next();
        dest= new Destination();
        dest.setId((String)tuple[0]);
        dest.setName((String)tuple[1]);
        dest.setLat((String)tuple[2]);
        dest.setLng((String)tuple[3]);
        destinations.add(dest);
    }

내가 찾은 다른 방법으로는 캐스트 문제가있었습니다.


무슨 "캐스트 문제"? 나는 항상 목록을 직접 캐스팅했습니다. 위의 내용이 어떻게 더 간결하고 안전한가요?
Giovanni Botta 2014

직접 전송할 수 없습니다. 이 대상에 Object에서 캐스팅하지 수 있기 때문에 문제를 던졌다
포파 안드레이을

Hibernate가 당신을 위해 구축 할 수 있다는 것을 알고 Destinstion있습니까? select new구문 사용 . 이것은 확실히 올바른 접근 방식이 아닙니다.
보리스 스파이더

나도 같은 경험을했습니다. 내 쿼리는 서로 연결되지 않은 여러 테이블의 다른 필드를 반환합니다. 그래서 나를 위해 일한 유일한 방법은 이것뿐이었습니다. 감사합니다 :)
Chintan Patel 2015

3
List<Person> list = new ArrayList<Person>();
Criteria criteria = this.getSessionFactory().getCurrentSession().createCriteria(Person.class);
for (final Object o : criteria.list()) {
    list.add((Person) o);
}

예, 이것은 기본적으로 Boris가 제안한 것과 동일한 '추악함'이며 루프 내에 캐스트가 있습니다.
LSerni

2

다음과 같은 ResultTransformer를 사용합니다.

public List<Foo> activeObjects() {
    Session s = acquireSession();
    Query   q = s.createQuery("from foo where active");
    q.setResultTransformer(Transformers.aliasToBean(Foo.class));
    return (List<Foo>) q.list();
}

1
지금 테스트 할 수는 없지만 ...이게 뭐가 바뀌나요? q여전히 Query이므로 q.list()여전히 원시 java.util.List유형입니다. 그러면 캐스트는 여전히 확인되지 않습니다. 개체 유형을 내부적으로 변경해도 아무 소용이 없습니다 ...
LSerni

예, 캐스트는 여전히 선택되어 있지 않지만 필드의 적절한 이름을 지정하면 resultTransformer를 설정하면 원하는 POJO로 개체를 캐스팅하는 작업이 수행됩니다. 이보기 에 유래 게시물 이 읽어 네이티브 쿼리를 사용하여 최대 절전 모드 참조 문서를
lakreqta을

from foo where active네이티브 쿼리 가 아닙니다 . 따라서 기본 매핑으로 충분하므로 결과 변환기가 필요하지 않습니다. 질문은 POJO 필드를 캐스팅하는 것이 아니라 결과 개체를 캐스팅하는 것입니다. 결과 변환기는 여기서 도움이되지 않습니다.
Tobias Liefke

0

적절한 방법은 Hibernate Transformer를 사용하는 것입니다.

public class StudentDTO {
private String studentName;
private String courseDescription;

public StudentDTO() { }  
...
} 

.

List resultWithAliasedBean = s.createSQLQuery(
"SELECT st.name as studentName, co.description as courseDescription " +
"FROM Enrolment e " +
"INNER JOIN Student st on e.studentId=st.studentId " +
"INNER JOIN Course co on e.courseCode=co.courseCode")
.setResultTransformer( Transformers.aliasToBean(StudentDTO.class))
.list();

StudentDTO dto =(StudentDTO) resultWithAliasedBean.get(0);

Object []를 통한 반복은 중복되며 약간의 성능 저하가 있습니다. 트랜스포머 사용에 대한 자세한 정보는 여기에서 찾을 수 있습니다. HQL 및 SQL 용 트랜스포머

더 간단한 솔루션을 찾고 있다면 즉시 사용할 수있는 맵 변환기를 사용할 수 있습니다.

List iter = s.createQuery(
"select e.student.name as studentName," +
"       e.course.description as courseDescription" +
"from   Enrolment as e")
.setResultTransformer( Transformers.ALIAS_TO_ENTITY_MAP )
.iterate();

String name = (Map)(iter.next()).get("studentName");

문제는 결과 변환에 관한 것이 아닙니다. 그것은 Query결과의 캐스팅에 관한 것이 었습니다 -당신의 예에서 여전히 필요합니다. 그리고 귀하의 예는 원본과 관련이 없습니다 from foo where active.
Tobias Liefke

0

그냥 Transformers를 사용하는 것 그것은 나를 위해 작동하지 않았습니다. 유형 캐스트 ​​예외가 발생했습니다.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) 목록 요소의 고정 MYEngityName 유형이 아닌 반환 목록 요소에서 객체 배열을 얻었 기 때문에 작동하지 않았습니다.

sqlQuery.addScalar(-)선택한 각 열과 해당 유형을 추가하고 특정 문자열 유형 열에 대해 해당 유형을 매핑 할 필요가 없을 때 다음과 같이 변경하면 저에게 효과적이었습니다. 처럼addScalar("langCode");

그리고 MYEngityName을 NextEnity와 조인 select *했습니다. Query에서 반환 목록에 Object 배열을 제공 할 수 없습니다.

아래 코드 샘플 :

session = ht.getSessionFactory().openSession();
                String sql = new StringBuffer("Select txnId,nft.mId,count,retryReason,langCode FROM  MYEngityName nft INNER JOIN NextEntity m on nft.mId  =  m.id where nft.txnId < ").append(lastTxnId)
                       .append(StringUtils.isNotBlank(regionalCountryOfService)? " And  m.countryOfService in ( "+ regionalCountryOfService +" )" :"")
                       .append(" order by nft.txnId desc").toString();
                SQLQuery sqlQuery = session.createSQLQuery(sql);
                sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class));
                sqlQuery.addScalar("txnId",Hibernate.LONG)
                        .addScalar("merchantId",Hibernate.INTEGER)
                        .addScalar("count",Hibernate.BYTE)
                        .addScalar("retryReason")
                        .addScalar("langCode");
                sqlQuery.setMaxResults(maxLimit);
                return sqlQuery.list();

도움이 될 수 있습니다. 이런 식으로 나를 위해 일합니다.


-1

여기서 가장 좋은 해결책을 찾았습니다. 이 문제의 핵심은 addEntity 메서드입니다.

public static void testSimpleSQL() {
    final Session session = sessionFactory.openSession();
    SQLQuery q = session.createSQLQuery("select * from ENTITY");
    q.addEntity(Entity.class);
    List<Entity> entities = q.list();
    for (Entity entity : entities) {
        System.out.println(entity);
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.