Hibernate Query.list()をList <Type>にキャストする「適切な」方法は何ですか?


84

私はHibernateの初心者であり、特定のフィルターに一致するオブジェクトのリストを返す簡単なメソッドを作成しています。List<Foo>自然な戻り値の型のようでした。

私が何をするにしても、醜いものを使わない限り、コンパイラを幸せにすることはできないようです@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)がパラメーター化された型を実装しないのは奇妙だと思うので、代わりに何か間違ったことをしているのは私だと思います。

オブジェクトのリストにHibernateの結果をキャストするのと非常によく似ていますが、ここでは「ハード」エラーはありません(システムはタイプFooを認識しており、SQLQueryではなくストレートクエリを使用しています)。だから喜びはありません。

Hibernate Class Cast Exceptionも有望に見えたので調べましたが、実際には何も得られないことに気付きましたException...私の問題は警告の問題です-コーディングスタイルです。

jboss.orgは、Hibernateのマニュアルや、いくつかのチュートリアルのドキュメントは内のトピックをカバーしていないよう詳細(または私は右の場所で検索しませんでした?)。彼らが詳細に入るとき、彼らはオンザフライキャスティングを使用します-そしてこれは公式のjboss.orgサイトになかったチュートリアルであるので、私は少し警戒しています。

コードは、コンパイルされると、明らかな問題なく実行されます...私が知っていることです...まだ; 結果は期待どおりです。

だから:私はこれを正しくやっていますか?明らかな何かが欠けていますか?それを行うための「公式」または「推奨」の方法はありますか?

回答:


101

短い答え@SuppressWarningsが正しい方法です。

長い答えですが、HibernateはメソッドListからrawを返します。ここを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
私はあなたの答えに賛成するだけで、もっと良い答えが来ることを望んでいました。-代わりに、私はこの問題は、両方のブルースEckel氏(Javaで思考)とロバート・セッジウィックによる「醜いキャスト」と呼ばれていSedgewick。stackoverflow.com/questions/509076/…も見つかりました。はぁ。
LSerni 2013

6
私はあなたの使い方が好きですfinal
Pavel 2013年

9
休止状態の人がタイプClass<?>inの引数を追加するとlist()、問題は解決する可能性があります。このような醜いAPIを使用するのは残念です。
Bin Wang

@BinWangの場合、安全でないキャストは別の場所で発生します。問題は解決されません。移動するだけです。言うまでもなく、HQLAPIは何年もの間事実上非推奨になっています。JPAには、Criteria QueryAPIと呼ばれるタイプセーフなクエリAPIがあります
ボリスのくも

2
@PeteyPabPro rawtypesを避けるべきであることに同意しますが、クエリの結果をとして扱うべきであることに同意しません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

これは、名前付きクエリ、ネイティブの名前付きクエリなどでも同じように機能します。対応するメソッドの名前は、バニラクエリを返すメソッドと同じです。戻り値の型がわかっている場合は、クエリの代わりにこれを使用してください。


1
ちなみに、これは、インラインで作成している純粋なネイティブクエリでは機能しません。返されるクエリは、typedqueryではなく、クエリのみです:(
Taugenichts

これで、エラーメッセージが消え、結果のリストでObjectオブジェクトの代わりに実際のオブジェクトが使用されます。すばらしい答えです。
ヨハネス

これはHibernate5.0でリリースされたようです。4.3.11には表示されませんが、次の記事では5.3を参照しています。wiki.openbravo.com/wiki/…2014年1月にそれを参照しているstackoverflowの投稿もあります:stackoverflow.com/a/21354639/854342
CurtisYallop18年

これはhibernate-jpa-2.0-apiの一部です。私は現在hibernate4.3で使用しているので、これはhibernate4.3で使用できます。
Taugenichts 2018

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 2013

2
>あなたはこれらの警告に耐えるか、それらを抑制する必要があります。間違っている警告を抑制することは常に良いことです。さもないと、抑制されていない間違った警告のスパムで正しい警告を見逃す可能性があります
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);
    }

私が見つけた他の方法では、私は問題を投げかけました


「キャストの問題」とは何ですか?私はいつもリストを直接キャストしましたが、上記はどのように簡潔または安全ですか?
ジョバンニボッタ2014

直接キャストすることはできません。オブジェクトから宛先へのキャストを作成できなかったため、キャストの問題が発生しました
Popa Andrei

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 2014年

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はまだaQueryであるためq.list()、未加工のjava.util.List型です。その後、キャストはまだチェックされていません。変更されたオブジェクト型の持つ内部的には...何も役に立つべきではありません
LSerni

はい、キャストはまだチェックされていませんが、フィールドに適切な名前を付けて、resultTransformerを設定すると、オブジェクトを目的のPOJOとしてキャストします。このstackoverflowの投稿参照し、ネイティブクエリの使用に関する
lakreqta 2015年

from foo where activeネイティブクエリではありません。したがって、デフォルトのマッピングで十分なので、結果トランスフォーマーは必要ありません。問題は、POJOフィールドのキャストではなく、結果オブジェクトのキャストに関するものです。結果トランスフォーマーはここでは役に立ちません。
Tobias Liefke 2015

0

適切な方法は、HibernateTransformersを使用することです。

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用のトランスフォーマー

さらに単純なソリューションを探している場合は、すぐに使用できるmap-transformerを使用できます。

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 2015

0

Transformersを使用するだけでは機能しませんでした。型キャストの例外が発生していました。

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) リスト要素の固定MYEngityNameタイプではなく、戻りリスト要素でオブジェクトの配列を取得していたため、機能しませんでした。

次の変更を行うと、機能しました。sqlQuery.addScalar(-)選択した各列とそのタイプを追加し、特定の文字列タイプの列については、そのタイプをマップする必要はありません。お気に入りaddScalar("langCode");

そして、MYEngityNameをNextEnityと結合しselect *ました。クエリだけでは、戻りリストにオブジェクトの配列が表示されません。

以下のコードサンプル:

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.