วิธีที่ "เหมาะสม" ในการส่ง 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ส่งคืน a java.util.Listเป็นประเภทดิบ ฉันคิดว่ามันแปลกที่ Hibernate (4.0.x) ล่าสุดจะไม่ใช้ประเภทที่กำหนดพารามิเตอร์ดังนั้นฉันจึงสงสัยว่าเป็นฉันแทนที่จะทำอะไรผิด

ดูเหมือนผลลัพธ์ Cast Hibernate ในรายการวัตถุแต่ที่นี่ฉันไม่มีข้อผิดพลาด "ยาก" (ระบบรู้จักประเภท Foo และฉันไม่ได้ใช้ SQLQuery แต่เป็นการสืบค้นแบบตรง) ดังนั้นไม่มีความสุข

ฉันได้ดูHibernate Class Cast Exception ด้วยเนื่องจากมันดูมีแนวโน้ม แต่แล้วฉันก็รู้ว่าฉันไม่ได้รับอะไรเลยException... ปัญหาของฉันเป็นเพียงคำเตือน - รูปแบบการเข้ารหัสถ้าคุณต้องการ

เอกสารใน jboss.org คู่มือไฮเบอร์เนตและแบบฝึกหัดต่างๆดูเหมือนจะไม่ครอบคลุมหัวข้อในรายละเอียดดังกล่าว (หรือฉันไม่ได้ค้นหาในสถานที่ที่ถูกต้อง?) เมื่อลงรายละเอียดแล้วพวกเขาจะใช้การแคสต์แบบทันที - และนี่คือบทช่วยสอนที่ไม่ได้อยู่ในเว็บไซต์ 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 (Thinking in Java) และ Robert Sedgewick - The Sedgewick ฉันยังพบstackoverflow.com/questions/509076/... เฮ้อ.
LSerni

6
ฉันชอบสไตล์การใช้งานของคุณfinal
Pavel

9
ถ้าพวกจำศีลเพิ่มอาร์กิวเมนต์ด้วย type Class<?>in list()ปัญหาก็จะแก้ไขได้ เป็นเรื่องน่าเสียดายที่ต้องใช้ API ที่น่าเกลียดเช่นนี้
Bin Wang

@BinWang จากนั้นนักแสดงที่ไม่ปลอดภัยจะเกิดขึ้นที่อื่นมันไม่ได้ช่วยแก้ปัญหา - เพียงแค่ย้ายมัน ไม่จำเป็นต้องพูดว่า HQL API ได้เลิกใช้งานอย่างมีประสิทธิภาพมาหลายปีแล้ว JPA มี API ชนิดของแบบสอบถามความปลอดภัยที่เรียกว่าเกณฑ์แบบสอบถาม API
Boris the Spider

2
@PeteyPabPro ในขณะที่ฉันยอมรับว่าควรหลีกเลี่ยง Rawtypes แต่ฉันไม่เห็นด้วยว่าผลลัพธ์ของแบบสอบถามควรถือเป็นไฟล์List<Object>. ควรแคสต์ผลลัพธ์ไปยังประเภทที่คาดไว้และควรเพิ่มการทดสอบหน่วยเพื่อให้แน่ใจว่าเคียวรีส่งคืนผลลัพธ์ที่ถูกต้อง เป็นที่ยอมรับไม่ได้ที่จะมีข้อผิดพลาดเมื่อมีการค้นหา " ในภายหลังในโค้ด " ตัวอย่างของคุณคือการโต้แย้งแนวทางปฏิบัติในการเขียนโค้ดที่ควรเป็นคำสาปแช่งในศตวรรษที่ 21 เป็นที่ฉันจะแนะนำไม่ยอมรับที่จะมีList<Object>.
Boris the Spider

26

ความละเอียดคือการใช้ TypedQuery แทน เมื่อสร้างแบบสอบถามจาก EntityManager แทนที่จะเรียกสิ่งนี้ว่า:

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

นอกจากนี้ยังใช้งานได้เหมือนกันสำหรับการสืบค้นที่มีชื่อการสืบค้นแบบเนทีฟและอื่น ๆ วิธีการที่เกี่ยวข้องมีชื่อเดียวกันกับคำค้นหาที่จะส่งคืนการสืบค้นวานิลลา เพียงใช้สิ่งนี้แทนแบบสอบถามเมื่อใดก็ตามที่คุณทราบประเภทการส่งคืน


1
หมายเหตุด้านข้างวิธีนี้ใช้ไม่ได้กับการสืบค้นแบบเนทีฟที่คุณกำลังสร้างแบบอินไลน์ ข้อความค้นหาที่ส่งคืนเป็นเพียงการสืบค้นเท่านั้นไม่ใช่คำค้นหาที่พิมพ์ :(
Taugenichts

ตอนนี้ข้อความแสดงข้อผิดพลาดหายไปและรายการผลลัพธ์ใช้วัตถุจริงแทนวัตถุ Object ..
Johannes

สิ่งนี้ดูเหมือนจะออกในโหมดไฮเบอร์เนต 5.0 ฉันไม่เห็นมันใน 4.3.11 แต่บทความต่อไปนี้อ้างถึง 5.3 wiki.openbravo.com/wiki/...ฉันยังเห็นโพสต์ StackOverflow มกราคม 2014 หมายถึงว่ามัน: 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
> คุณต้องดำเนินชีวิตตามคำเตือนเหล่านี้หรือกดขี่พวกเขา จะดีกว่าเสมอหากกดคำเตือนที่ไม่ถูกต้องหรือคุณอาจพลาดคำเตือนที่ถูกต้องในสแปมของคำเตือนที่ไม่ถูกต้อง
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 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

ฉันไม่สามารถแคสต์โดยตรง มีปัญหาในการแคสต์เนื่องจากไม่สามารถส่งจาก Object to Destination ได้
Popa Andrei

คุณรู้หรือไม่ว่าไฮเบอร์เนตสามารถสร้างสิ่งใหม่Destinstionให้คุณได้ใช่ไหม การใช้select newไวยากรณ์ นี่ไม่ใช่แนวทางที่ถูกต้องอย่างแน่นอน
Boris the Spider

ฉันก็มีประสบการณ์เดียวกัน เนื่องจากข้อความค้นหาของฉันส่งคืนเขตข้อมูลที่แตกต่างกันจากหลายตารางซึ่งไม่ได้เชื่อมต่อกัน วิธีเดียวที่ใช้ได้ผลสำหรับฉันคือวิธีนี้ ขอบคุณ :)
Chintan Patel

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);
}

ใช่นี่เป็น 'ความอัปลักษณ์' แบบเดียวกับที่บอริสแนะนำโดยมีนักแสดงอยู่ในวง
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 ที่คุณต้องการ ดูโพสต์ stackoverflowนี้และอ่านเอกสารอ้างอิงไฮเบอร์เนตเกี่ยวกับการใช้แบบสอบถามเนทีฟ
lakreqta

from foo where activeคือไม่แบบสอบถามพื้นเมือง ดังนั้นจึงไม่จำเป็นต้องใช้หม้อแปลงผลลัพธ์เนื่องจากการทำแผนที่เริ่มต้นจะเพียงพอ คำถามไม่ได้เกี่ยวกับการแคสต์ฟิลด์ POJO แต่เกี่ยวกับการแคสต์ออบเจ็กต์ผลลัพธ์ หม้อแปลงผลลัพธ์จะไม่ช่วยที่นี่
Tobias Liefke

0

วิธีที่เหมาะสมคือใช้ Hibernate Transformers:

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);

การทำซ้ำ througth Object [] นั้นซ้ำซ้อนและอาจมีโทษด้านประสิทธิภาพ ข้อมูลโดยละเอียดเกี่ยวกับการใช้ transofrmers คุณจะพบได้ที่นี่: Transformers สำหรับ 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)) ไม่ทำงานเนื่องจากฉันได้รับ Array of Object ในองค์ประกอบรายการส่งคืนไม่ใช่ประเภทรายการ 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.