Cách “thích hợp” để truyền Hibernate Query.list () sang List <Type> là gì?


84

Tôi là người mới sử dụng Hibernate và tôi đang viết một phương pháp đơn giản để trả về danh sách các đối tượng phù hợp với một bộ lọc cụ thể. List<Foo>dường như là một kiểu trả lại tự nhiên.

Dù tôi làm gì đi nữa, tôi dường như không thể làm cho trình biên dịch hài lòng, trừ khi tôi thuê một người xấu xí @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();
    }
}

Tôi muốn thoát khỏi điều đóSuppressWarnings . Nhưng nếu tôi làm vậy, tôi sẽ nhận được cảnh báo

Warning: Unchecked cast from List to List<Foo>

(Tôi có thể bỏ qua nó, nhưng tôi không muốn lấy nó ngay từ đầu) và nếu tôi xóa tên chung để phù hợp với .list()kiểu trả về, tôi sẽ nhận được cảnh báo

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

Tôi nhận thấy rằng org.hibernate.mapping không khai báo a List; nhưng nó hoàn toàn là một kiểu khác - Querytrả về a java.util.List, dưới dạng kiểu thô. Tôi thấy thật kỳ lạ khi một Hibernate (4.0.x) gần đây sẽ không triển khai các kiểu tham số hóa, vì vậy tôi nghi ngờ rằng thay vào đó tôi đã làm sai điều gì đó.

Nó trông rất giống kết quả Cast Hibernate tới một danh sách các đối tượng , nhưng ở đây tôi không gặp lỗi "khó" nào (hệ thống biết kiểu Foo và tôi không sử dụng SQLQuery mà là một Query thẳng). Vì vậy, không có niềm vui.

Tôi cũng đã xem xét Hibernate Class Cast Exception vì nó trông có vẻ hứa hẹn, nhưng sau đó tôi nhận ra rằng tôi thực sự không nhận được bất kỳ Exception... vấn đề của tôi chỉ là một cảnh báo - một phong cách mã hóa, nếu bạn muốn.

Tài liệu trên jboss.org, hướng dẫn sử dụng Hibernate và một số hướng dẫn dường như không đề cập đến chủ đề một cách chi tiết như vậy (hoặc tôi đã không tìm kiếm ở đúng nơi?). Khi họ đi vào chi tiết, họ sử dụng tính năng truyền trực tiếp - và điều này trên các hướng dẫn không có trên trang jboss.org chính thức, vì vậy tôi hơi cảnh giác.

Mã, sau khi được biên dịch, chạy mà không có vấn đề gì rõ ràng ... mà tôi biết ... chưa; và kết quả là những điều mong đợi.

Vì vậy: tôi làm điều này có đúng không? Tôi đang thiếu một cái gì đó rõ ràng? Có một "chính thức" hoặc "đề nghị" cách làm như thế ?

Câu trả lời:


101

Câu trả lời ngắn gọn @SuppressWarningslà con đường phù hợp để đi.

Câu trả lời dài, Hibernate trả về một nguyên Listtừ Query.listphương thức, xem tại đây . Đây không phải là một lỗi với Hibernate hoặc một cái gì đó có thể được giải quyết, loại được trả về bởi truy vấn không được biết tại thời điểm biên dịch.

Do đó khi bạn viết

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

Bạn đang truyền không an toàn từ Listđến List<MyObject>- điều này không thể tránh được.

Không có cách nào bạn có thể tiến hành bó bột một cách an toàn vì nó List có thể chứa bất cứ thứ gì.

Cách duy nhất để làm cho lỗi biến mất, thậm chí còn xấu hơn

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

4
Tôi sẽ chỉ ủng hộ câu trả lời của bạn, hy vọng một câu trả lời tốt hơn sẽ đến. Thay vào đó, tôi phát hiện ra vấn đề này được cả Bruce Eckel (Suy nghĩ trong Java) và Robert Sedgewick - người Sedgewick gọi là "diễn viên xấu xí" . Tôi cũng tìm thấy stackoverflow.com/questions/509076/… . Thở dài.
LSerni

6
Tôi giống như phong cách của bạn sử dụngfinal
Pavel

9
Nếu kẻ ngủ đông thêm một cuộc tranh cãi với loại Class<?>trong list(), vấn đề có thể được giải quyết. Thật tiếc khi sử dụng một API xấu xí như vậy.
Bin Wang,

@BinWang thì diễn viên không an toàn sẽ xảy ra ở một nơi khác, nó không giải quyết được vấn đề - nó chỉ di chuyển nó. Không cần phải nói API HQL đã không còn được sử dụng trong nhiều năm nay. JPA có một API truy vấn an toàn loại được gọi là API truy vấn tiêu chí .
Boris the Spider

2
@PeteyPabPro Mặc dù tôi đồng ý rằng nên tránh các kiểu thô, tôi không đồng ý rằng kết quả của một truy vấn nên được coi là a List<Object>. Các kết quả phải được chuyển sang kiểu mong đợi và các bài kiểm tra đơn vị phải được thêm vào để đảm bảo rằng truy vấn trả về kết quả phù hợp. Không thể chấp nhận được lỗi với các truy vấn xuất hiện " sau trong mã ". Ví dụ của bạn là một lập luận chống lại các thực hành mã hóa đáng ra phải trở thành bệnh phổ biến trong thế kỷ 21. Đó là, tôi sẽ đề nghị, không bao giờ chấp nhận được để có một List<Object>.
Boris the Spider,

26

Giải pháp là sử dụng TypedQuery thay thế. Khi tạo một truy vấn từ EntityManager, hãy gọi nó như sau:

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

Điều này cũng hoạt động tương tự đối với các truy vấn được đặt tên, truy vấn có tên gốc, v.v. Các phương thức tương ứng có cùng tên với các phương thức sẽ trả về truy vấn vani. Chỉ cần sử dụng điều này thay vì Truy vấn bất cứ khi nào bạn biết kiểu trả về.


1
Lưu ý thêm, điều này không hoạt động đối với các truy vấn thuần túy gốc mà bạn đang tạo nội tuyến. Truy vấn được trả về chỉ là một truy vấn, không phải là một truy vấn đánh máy :(
Taugenichts

Bây giờ, thông báo lỗi đã biến mất và danh sách kết quả sử dụng các đối tượng thực tế thay vì đối tượng Đối tượng .. câu trả lời tuyệt vời!
Johannes

Điều này dường như được phát hành trong hibernate 5.0. Tôi không thấy nó trong 4.3.11 nhưng bài viết sau đề cập đến 5.3. wiki.openbravo.com/wiki/… Tôi cũng thấy một bài đăng stackoverflow tháng 1 năm 2014 đề cập đến nó: stackoverflow.com/a/21354639/854342 .
Curtis Yallop

Nó là một phần của hibernate-jpa-2.0-api. Bạn có thể sử dụng nó với hibernate 4.3, vì tôi hiện đang sử dụng nó trên hibernate 4.3.
Taugenichts

6

Bạn có thể tránh cảnh báo trình biên dịch với các cách giải quyết như sau:

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

Nhưng có một số vấn đề với mã này:

  • đã tạo ArrayList thừa
  • vòng lặp không cần thiết trên tất cả các phần tử được trả về từ truy vấn
  • mã dài hơn.

Và sự khác biệt chỉ là thẩm mỹ, vì vậy việc sử dụng những cách giải quyết như vậy - theo ý kiến ​​của tôi - là vô nghĩa.

Bạn phải sống với những cảnh báo này hoặc ngăn chặn chúng.


1
Tôi đồng ý với sự vô nghĩa. Tôi sẽ ngăn chặn, ngay cả khi nó đi ngược lại với ngũ cốc của tôi. Cảm ơn tất cả như nhau và +1.
LSerni

2
> Bạn phải sống với những cảnh báo này hoặc khó chịu với chúng. Đó là luôn luôn tốt hơn để cảnh báo đàn áp đó là sai, hoặc bạn có thể bỏ lỡ cảnh báo ngay trong một thư rác của các cảnh báo sai unsupressed
SpongeBobFan

6

Để trả lời câu hỏi của bạn, không có "cách thích hợp" để làm điều đó. Bây giờ nếu nó chỉ là cảnh báo làm phiền bạn, thì cách tốt nhất để tránh sự gia tăng của nó là gói Query.list()phương thức này thành một DAO:

public class MyDAO {

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

Bằng cách này bạn có thể sử dụng @SuppressWarnings("unchecked")một lần duy nhất.


Chào mừng bạn đến với Stack Overflow ! Dù sao, đừng quên tham gia chuyến tham quan
Sнаđошƒаӽ

3

Cách duy nhất làm việc với tôi là với một 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);
    }

Với các phương pháp khác mà tôi tìm thấy, tôi đã gặp sự cố


"Vấn đề cast" là gì? Tôi luôn chỉ chọn danh sách trực tiếp, làm thế nào ở trên ngắn gọn hơn hoặc an toàn hơn?
Giovanni Botta

Tôi không thể truyền trực tiếp. Bị quăng vấn đề bởi vì nó không thể làm cho đúc từ Object để Destination
Popa Andrei

Bạn biết Hibernate có thể xây dựng một Destinstioncho bạn phải không? Sử dụng select newcú pháp. Đây chắc chắn không phải là cách tiếp cận đúng.
Boris the Spider,

Tôi cũng đã có kinh nghiệm tương tự. Khi truy vấn của tôi trả về các trường khác nhau từ nhiều bảng không được kết nối với nhau. Vì vậy, cách duy nhất hiệu quả với tôi là cách này. Cảm ơn :)
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);
}

Vâng, về cơ bản đây là 'sự xấu xí' do Boris đề xuất, với một dàn diễn viên trong vòng lặp.
LSerni

2

Bạn sử dụng một ResultTransformer như vậy:

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
Tôi không thể kiểm tra cái này bây giờ, nhưng ... cái này thay đổi cái gì? qvẫn là một Queryvà do đó q.list()vẫn là một java.util.Listloại thô . Dàn diễn viên sau đó vẫn chưa được kiểm tra; có loại đối tượng thay đổi trong nội bộ nên tận dụng không có gì ...
LSerni

Có, quá trình truyền vẫn chưa được chọn, nhưng với việc đặt tên thích hợp cho các trường của bạn, việc đặt một resultTransformer sẽ thực hiện công việc truyền các Đối tượng như POJO mong muốn của bạn. Xem này stackoverflow bài và đọc ngủ đông tham khảo doc về việc sử dụng các truy vấn nguồn gốc
lakreqta

from foo where activekhông một truy vấn nguồn gốc. Vì vậy, không cần biến áp kết quả, vì ánh xạ mặc định là đủ. Câu hỏi không phải về truyền các trường POJO, mà là về truyền đối tượng kết quả. Một máy biến áp kết quả sẽ không giúp được gì ở đây.
Tobias Liefke

0

Cách thích hợp là sử dụng 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);

Việc lặp lại Object [] là không cần thiết và sẽ có một số hình phạt về hiệu suất. Thông tin chi tiết về cách sử dụng bộ biến áp bạn sẽ tìm thấy ở đây: Bộ biến áp cho HQL và SQL

Nếu bạn đang tìm kiếm giải pháp đơn giản hơn, bạn có thể sử dụng máy biến áp out-of-the-box-map:

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

Câu hỏi không phải về chuyển đổi kết quả. Đó là về đúc Querykết quả - điều vẫn cần thiết trong ví dụ của bạn. Và ví dụ của bạn không liên quan gì đến bản gốc from foo where active.
Tobias Liefke

0

Chỉ sử dụng Transformers Nó không hoạt động với tôi, tôi đã nhận được ngoại lệ loại cast.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) không hoạt động vì tôi đang nhận được Mảng đối tượng trong phần tử danh sách trả về không phải là loại MYEngityName cố định của phần tử danh sách.

Nó hoạt động với tôi khi tôi thực hiện các thay đổi sau Khi tôi đã thêm sqlQuery.addScalar(-)từng cột đã chọn và loại của nó và đối với cột loại Chuỗi cụ thể, chúng tôi không phải ánh xạ loại của nó. giốngaddScalar("langCode");

Và tôi đã tham gia MYEngityName với NextEnity, chúng tôi không thể chỉ select *trong Truy vấn, nó sẽ cung cấp mảng Đối tượng trong danh sách trả về.

Mẫu mã dưới đây:

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

Nó có thể giúp một số người. theo cách này làm việc cho tôi.


-1

Tôi đã tìm thấy giải pháp tốt nhất ở đây , mấu chốt của vấn đề này là phương thức 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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.