JPA getSingleResult () hoặc null


136

Tôi có một insertOrUpdatephương thức chèn vào Entitykhi nó không tồn tại hoặc cập nhật nó nếu có. Để kích hoạt điều này, tôi phải findByIdAndForeignKey, nếu nó trả về nullchèn nếu không thì cập nhật. Vấn đề là làm thế nào để tôi kiểm tra nếu nó tồn tại? Vì vậy, tôi đã cố gắng getSingleResult. Nhưng nó ném một ngoại lệ nếu

public Profile findByUserNameAndPropertyName(String userName, String propertyName) {
    String namedQuery = Profile.class.getSimpleName() + ".findByUserNameAndPropertyName";
    Query query = entityManager.createNamedQuery(namedQuery);
    query.setParameter("name", userName);
    query.setParameter("propName", propertyName);
    Object result = query.getSingleResult();
    if (result == null) return null;
    return (Profile) result;
}

nhưng getSingleResultném một Exception.

Cảm ơn

Câu trả lời:


266

Ném một ngoại lệ là cách getSingleResult()chỉ ra nó không thể được tìm thấy. Cá nhân tôi không thể chịu được loại API này. Nó buộc xử lý ngoại lệ giả không có lợi ích thực sự. Bạn chỉ cần bọc mã trong một khối thử.

Ngoài ra, bạn có thể truy vấn danh sách và xem nếu nó trống. Đó không phải là một ngoại lệ. Trên thực tế vì bạn không thực hiện tra cứu khóa chính về mặt kỹ thuật nên có thể có nhiều kết quả (ngay cả khi một, cả hai hoặc kết hợp các khóa ngoại hoặc ràng buộc của bạn làm cho điều này không thể thực hiện được trong thực tế), vì vậy đây có lẽ là giải pháp phù hợp hơn.


115
Tôi không đồng ý, getSingleResult()được sử dụng trong các tình huống như: " Tôi hoàn toàn chắc chắn rằng hồ sơ này tồn tại. Hãy bắn tôi nếu không ". Tôi không muốn kiểm tra nullmỗi lần tôi sử dụng phương pháp này vì tôi chắc chắn rằng nó sẽ không trả lại. Nếu không, nó gây ra rất nhiều chương trình nồi hơi và phòng thủ. Và nếu bản ghi thực sự không tồn tại (ngược lại với những gì chúng ta đã giả định), sẽ tốt hơn nhiều NoResultExceptionso với NullPointerExceptionvài dòng sau này. Tất nhiên có hai phiên bản getSingleResult()sẽ rất tuyệt, nhưng nếu tôi phải chọn một ...
Tomasz Nurkiewicz 17/03/2016

8
@cletus Null thực sự là một giá trị trả về hợp lệ cho cơ sở dữ liệu.
Bill Rosmus

12
@TomaszNurkiewicz đó là một điểm tốt. Tuy nhiên, có vẻ như nên có một số loại "getSingleResultOrNull". Tôi đoán bạn có thể tạo ra một trình bao bọc cho như vậy.
cbmeek

2
Dưới đây là một số thông tin về lợi ích của ngoại lệ bắt đầu được ném từ getSingleResult (): Truy vấn có thể được sử dụng để truy xuất hầu hết mọi thứ, kể cả giá trị của một cột trong một hàng. Nếu getSingleResult () sẽ trả về null, bạn không thể biết liệu truy vấn không khớp với bất kỳ hàng nào hay liệu truy vấn có khớp với một hàng không nhưng cột được chọn có chứa null làm giá trị của nó không. từ: stackoverflow.com/a/12155901/1242321
user1242321

5
Nó sẽ trả về Tùy chọn <T>. Đó là một cách tốt để chỉ ra các giá trị còn thiếu.
Vivek Kothari

33

Tôi gói gọn logic trong phương thức trợ giúp sau.

public class JpaResultHelper {
    public static Object getSingleResultOrNull(Query query){
        List results = query.getResultList();
        if (results.isEmpty()) return null;
        else if (results.size() == 1) return results.get(0);
        throw new NonUniqueResultException();
    }
}

2
Lưu ý rằng bạn có thể tối ưu hơn một chút bằng cách gọi Query.setMaxResults (1). Đáng buồn thay, vì Truy vấn có trạng thái, bạn sẽ muốn nắm bắt giá trị của Query.getMaxResults () và sửa đối tượng trong khối thử cuối cùng và có thể chỉ thất bại hoàn toàn nếu Query.getFirstResult () trả về bất cứ điều gì thú vị.
Patrick Linskey

đó là cách chúng tôi đã thực hiện nó trong dự án của chúng tôi. Không bao giờ có bất kỳ vấn đề nào với việc triển khai này
walv

25

Hãy thử điều này trong Java 8:

Optional first = query.getResultList().stream().findFirst();

3
Bạn có thể thoát khỏi Tùy chọn bằng cách thêm.orElse(null)
Justin Rowe

24

Đây là một lựa chọn tốt để làm điều này:

public static <T> T getSingleResult(TypedQuery<T> query) {
    query.setMaxResults(1);
    List<T> list = query.getResultList();
    if (list == null || list.isEmpty()) {
        return null;
    }

    return list.get(0);
}

2
Khéo léo! TypedQuery<T>Mặc dù vậy, tôi chấp nhận , trong trường hợp getResultList()đó, sau đó đã được gõ chính xác là a List<T>.
Rupi

Kết hợp với fetch()thực thể có thể không hoàn toàn dân cư. Xem stackoverflow.com/a/39235328/661414
Leukipp

1
Đây là một cách tiếp cận rất tốt đẹp. Lưu ý rằng setMaxResults()có một giao diện trôi chảy để bạn có thể viết query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Đây phải là lược đồ cuộc gọi hiệu quả nhất trong Java 8+.
Dirk Hillbrecht

17

Spring có một phương thức tiện ích cho việc này:

TypedQuery<Profile> query = em.createNamedQuery(namedQuery, Profile.class);
...
return org.springframework.dao.support.DataAccessUtils.singleResult(query.getResultList());

15

Tôi đã hoàn thành (trong Java 8):

query.getResultList().stream().findFirst().orElse(null);

ý của bạn là gì bởi truy vấn?
Enrico Giurin

Ý bạn là HibernateQuery? Nếu tôi muốn sử dụng api JPA thuần túy thì sao? Không có một phương pháp nào như vậy trong javax.persistence.Query
Enrico Giurin

2
@EnricoGiurin, tôi đã chỉnh sửa đoạn trích. Làm việc tốt Không thử bắt và không kiểm tra list.size. Dung dịch lót tốt nhất.
LovaBill

10

Từ JPA 2.2 , thay vì .getResultList()và kiểm tra xem danh sách có trống không hoặc tạo luồng bạn có thể trả lại luồng và lấy phần tử đầu tiên.

.getResultStream()
.findFirst()
.orElse(null);

7

Nếu bạn muốn sử dụng cơ chế try / Catch để xử lý vấn đề này .. thì nó có thể được sử dụng để hành động như if / other. Tôi đã sử dụng thử / bắt để thêm bản ghi mới khi tôi không tìm thấy bản ghi hiện có.

try {  //if part

    record = query.getSingleResult();   
    //use the record from the fetched result.
}
catch(NoResultException e){ //else part
    //create a new record.
    record = new Record();
    //.........
    entityManager.persist(record); 
}

6

Đây là phiên bản đánh máy / tổng quát, dựa trên triển khai của Rodrigo IronMan:

 public static <T> T getSingleResultOrNull(TypedQuery<T> query) {
    query.setMaxResults(1);
    List<T> list = query.getResultList();
    if (list.isEmpty()) {
        return null;
    }
    return list.get(0);
}

5

Có một cách khác mà tôi muốn giới thiệu:

Query query = em.createQuery("your query");
List<Element> elementList = query.getResultList();
return CollectionUtils.isEmpty(elementList ) ? null : elementList.get(0);

Điều này bảo vệ chống lại ngoại lệ con trỏ Null, đảm bảo chỉ có 1 kết quả được trả về.


4

Vì vậy, đừng làm điều đó!

Bạn có hai lựa chọn:

  1. Chạy một lựa chọn để có được COUNT của tập kết quả của bạn và chỉ lấy dữ liệu nếu số này khác không; hoặc là

  2. Sử dụng loại truy vấn khác (được đặt kết quả) và kiểm tra xem nó có 0 hoặc nhiều kết quả không. Nó nên có 1, vì vậy hãy rút nó ra khỏi bộ sưu tập kết quả của bạn và bạn đã hoàn thành.

Tôi sẽ đi với đề nghị thứ hai, đồng ý với Cletus. Nó cho hiệu suất tốt hơn (có khả năng) 2 truy vấn. Cũng ít làm việc.


1
Tùy chọn 3 Thử / bắt NoResultException
Ced

3

Kết hợp các bit hữu ích của các câu trả lời hiện có (giới hạn số lượng kết quả, kiểm tra xem kết quả đó là duy nhất) và sử dụng tên phương thức estabilshed (Hibernate), chúng tôi nhận được:

/**
 * Return a single instance that matches the query, or null if the query returns no results.
 *
 * @param query query (required)
 * @param <T> result record type
 * @return record or null
 */
public static <T> T uniqueResult(@NotNull TypedQuery<T> query) {
    List<T> results = query.setMaxResults(2).getResultList();
    if (results.size() > 1) throw new NonUniqueResultException();
    return results.isEmpty() ? null : results.get(0);
}

3

Phương thức không có giấy tờ uniqueResultOptionaltrong org.hibernate.query.Query nên thực hiện thủ thuật. Thay vì phải bắt một NoResultExceptionbạn chỉ có thể gọi query.uniqueResultOptional().orElse(null).


2

Tôi đã giải quyết điều này bằng cách sử dụng List<?> myList = query.getResultList();và kiểm tra xem có myList.size()bằng không.


1

Đây là logic giống như những người khác đã đề xuất (lấy resultList, trả về phần tử duy nhất hoặc null), sử dụng Google Guava và TypedQuery.

public static <T> getSingleResultOrNull(final TypedQuery<T> query) {
    return Iterables.getOnlyElement(query.getResultList(), null); 
}

Lưu ý rằng Guava sẽ trả về IllegalArgumentException không trực quan nếu tập kết quả có nhiều hơn một kết quả. (Ngoại lệ có ý nghĩa đối với các máy khách của getOnlyEuity (), vì nó lấy danh sách kết quả làm đối số của nó, nhưng ít hiểu đối với các máy khách của getSingleResultOrNull ().)


1

Đây là một phần mở rộng khác, lần này là trong Scala.

customerQuery.getSingleOrNone match {
  case Some(c) => // ...
  case None    => // ...
}

Với ma cô này:

import javax.persistence.{NonUniqueResultException, TypedQuery}
import scala.collection.JavaConversions._

object Implicits {

  class RichTypedQuery[T](q: TypedQuery[T]) {

    def getSingleOrNone : Option[T] = {

      val results = q.setMaxResults(2).getResultList

      if (results.isEmpty)
        None
      else if (results.size == 1)
        Some(results.head)
      else
        throw new NonUniqueResultException()
    }
  }

  implicit def query2RichQuery[T](q: TypedQuery[T]) = new RichTypedQuery[T](q)
}

1

Nhìn mã này:

return query.getResultList().stream().findFirst().orElse(null);

Khi findFirst()được gọi có lẽ có thể được ném NullPulumException.

aproach tốt nhất là:

return query.getResultList().stream().filter(Objects::nonNull).findFirst().orElse(null);


0

Vì vậy, tất cả các giải pháp "cố gắng viết lại mà không có ngoại lệ" trong trang này có một vấn đề nhỏ. Hoặc là nó không ném ngoại lệ NonUnique, cũng không ném nó trong một số trường hợp sai (xem bên dưới).

Tôi nghĩ rằng giải pháp thích hợp là (có thể) này:

public static <L> L getSingleResultOrNull(TypedQuery<L> query) {
    List<L> results = query.getResultList();
    L foundEntity = null;
    if(!results.isEmpty()) {
        foundEntity = results.get(0);
    }
    if(results.size() > 1) {
        for(L result : results) {
            if(result != foundEntity) {
                throw new NonUniqueResultException();
            }
        }
    }
    return foundEntity;
}

Nó trả về bằng null nếu có 0 phần tử trong danh sách, trả về nonunique nếu có các phần tử khác nhau trong danh sách, nhưng không trả về nonunique khi một trong các lựa chọn của bạn không được thiết kế đúng và trả về cùng một đối tượng sau đó một lần.

Hãy bình luận.


0

Tôi đã đạt được điều này bằng cách lấy danh sách kết quả sau đó kiểm tra xem nó có trống không

public boolean exist(String value) {
        List<Object> options = getEntityManager().createNamedQuery("AppUsers.findByEmail").setParameter('email', value).getResultList();
        return !options.isEmpty();
    }

Thật là khó chịu getSingleResult() ném ngoại lệ

Ném:

  1. NoResultException - nếu không có kết quả
  2. NonUniqueResultException - nếu có nhiều hơn một kết quả và một số ngoại lệ khác mà bạn có thể nhận thêm thông tin từ tài liệu của họ

-3

Đó là công việc với tôi:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
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.