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.