JPA getSingleResult () หรือ null


136

ฉันมีinsertOrUpdateวิธีการแทรกEntityเมื่อไม่มีหรืออัปเดตหากมี เพื่อเปิดใช้งานนี้ฉันต้องfindByIdAndForeignKeyถ้ามันกลับมาnullแทรกถ้าไม่แล้วอัปเดต ปัญหาคือฉันจะตรวจสอบว่ามีอยู่ได้อย่างไร? getSingleResultดังนั้นผมจึงพยายาม แต่มันก็มีข้อยกเว้นถ้า

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

แต่พ่นgetSingleResultException

ขอบคุณ

คำตอบ:


266

การโยนข้อยกเว้นเป็นอย่างไร getSingleResult()บ่งบอกว่าหาไม่พบ โดยส่วนตัวฉันไม่สามารถยืน API ประเภทนี้ได้ มันบังคับให้มีการจัดการข้อยกเว้นปลอมเพื่อไม่มีประโยชน์จริง คุณต้องล้อมโค้ดไว้ในบล็อค try-catch

หรือคุณสามารถค้นหารายการและดูว่าว่างหรือไม่ ไม่ได้เป็นการยกเว้น อันที่จริงแล้วเนื่องจากคุณไม่ได้ทำการค้นหาคีย์หลักในทางเทคนิคอาจมีผลลัพธ์หลายรายการ (แม้ว่าจะมีทั้งแบบผสมผสานหรือการรวมกันของคีย์หรือข้อ จำกัด ต่างประเทศทำให้เป็นไปไม่ได้ในทางปฏิบัติ) ดังนั้นนี่อาจเป็นทางออกที่เหมาะสมกว่า


115
ฉันไม่เห็นด้วยgetSingleResult()มีการใช้งานในสถานการณ์เช่น: " ฉันมั่นใจอย่างยิ่งว่าบันทึกนี้มีอยู่โปรดยิงฉันหากไม่เป็นเช่นนั้น " ฉันไม่ต้องการทดสอบnullทุกครั้งที่ฉันใช้วิธีนี้เพราะฉันมั่นใจว่ามันจะไม่ส่งคืน มิฉะนั้นจะทำให้เกิดการเขียนโปรแกรมสำเร็จรูปจำนวนมากและการป้องกัน และถ้าบันทึกไม่มีอยู่จริง (ตรงข้ามกับสิ่งที่เราคิดไว้) มันจะดีกว่ามากถ้าNoResultExceptionเทียบกับNullPointerExceptionสองสามบรรทัดในภายหลัง แน่นอนมีสองรุ่นgetSingleResult()จะน่ากลัว แต่ถ้าผมต้องเลือกอย่างใดอย่างหนึ่ง ...
Tomasz Nurkiewicz

8
@cletus Null เป็นค่าส่งคืนที่ถูกต้องสำหรับฐานข้อมูล
Bill Rosmus

12
@TomaszNurkiewicz นั่นเป็นจุดที่ดี อย่างไรก็ตามดูเหมือนว่าควรมี "getSingleResultOrNull" บางประเภท ฉันคิดว่าคุณสามารถสร้างเสื้อคลุมสำหรับเช่น
cbmeeks

2
นี่คือข้อมูลบางส่วนในแง่ของผลประโยชน์ของข้อยกเว้นเริ่มต้นจาก getSingleResult (): แบบสอบถามสามารถใช้เพื่อดึงข้อมูลเกือบทุกอย่างรวมถึงค่าของคอลัมน์เดียวในแถวเดียว ถ้า getSingleResult () จะส่งคืน null คุณไม่สามารถบอกได้ว่าแบบสอบถามไม่ตรงกับแถวใด ๆ หรือไม่ว่าแบบสอบถามตรงกับแถว แต่คอลัมน์ที่เลือกมีค่า null เป็นค่าหรือไม่ จาก: stackoverflow.com/a/12155901/1242321
user1242321

5
มันควรจะส่งคืนตัวเลือก <T> นั่นเป็นวิธีที่ดีในการระบุค่าที่ขาดหายไป
Vivek Kothari

33

ฉันสรุปเหตุผลในวิธีการช่วยเหลือดังต่อไปนี้

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
โปรดทราบว่าคุณสามารถเพิ่มประสิทธิภาพได้อีกเล็กน้อยโดยการเรียก Query.setMaxResults (1) น่าเศร้าเนื่องจาก Query เป็น stateful คุณจะต้องเก็บค่าของ Query.getMaxResults () และแก้ไขวัตถุในการลองบล็อกในที่สุดและอาจล้มเหลวโดยสิ้นเชิงถ้า Query.getFirstResult () ส่งคืนสิ่งที่น่าสนใจ
Patrick Linskey

นั่นคือวิธีที่เรานำไปใช้ในโครงการของเรา ไม่เคยมีปัญหาใด ๆ กับการใช้งานนี้
walv

25

ลองสิ่งนี้ใน Java 8:

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

3
คุณสามารถกำจัดตัวเลือกโดยเพิ่ม.orElse(null)
Justin Rowe

24

นี่เป็นตัวเลือกที่ดีสำหรับการทำสิ่งนี้:

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
เรียบร้อย! ฉันยอมรับTypedQuery<T>แต่ในกรณีที่อยู่ในนั้นแล้วพิมพ์อย่างถูกต้องเป็นgetResultList() List<T>
รูปี

เมื่อใช้ร่วมกับfetch()เอนทิตีอาจไม่ได้รับการเติมข้อมูลอย่างสมบูรณ์ ดูstackoverflow.com/a/39235828/661414
Leukipp

1
นี่เป็นวิธีการที่ดีมาก โปรดทราบว่ามีอินเตอร์เฟซได้อย่างคล่องแคล่วเพื่อให้คุณสามารถเขียนsetMaxResults() query.setMaxResults(1).getResultList().stream().findFirst().orElse(null)นี่ควรเป็นรูปแบบการโทรที่มีประสิทธิภาพที่สุดใน Java 8+
Dirk Hillbrecht


15

ฉันทำเสร็จแล้ว (ใน Java 8):

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

สิ่งที่คุณหมายถึงการสอบถาม?
Enrico Giurin

คุณหมายถึง HibernateQuery? จะเป็นอย่างไรถ้าฉันต้องการใช้ JPA api บริสุทธิ์ ไม่มีวิธีการเช่นนี้ใน javax.persistence.Query
Enrico Giurin

2
@EnricoGiurin ฉันแก้ไขตัวอย่างแล้ว ทำงานได้ดี ไม่ลองจับและไม่มีรายการตรวจสอบขนาด วิธีซับที่อร่อยที่สุดหนึ่งเดียว
LovaBill

10

จาก JPA 2.2แทนที่จะเป็น.getResultList()และตรวจสอบว่ารายการว่างเปล่าหรือสร้างกระแสคุณสามารถคืนค่าสตรีมและใช้องค์ประกอบแรก

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

7

หากคุณต้องการใช้กลไกลอง / จับเพื่อจัดการกับปัญหานี้ .. ก็สามารถใช้เพื่อทำหน้าที่เหมือน if / else ฉันใช้ try / catch เพื่อเพิ่มระเบียนใหม่เมื่อฉันไม่พบระเบียนที่มีอยู่

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

นี่คือเวอร์ชันสำหรับพิมพ์ / ข้อมูลทั่วไปตามการใช้งานของ 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

มีทางเลือกอื่นที่ฉันอยากจะแนะนำ:

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

การป้องกันจาก Null Pointer Exception นี้รับประกันผลลัพธ์เพียง 1 รายการเท่านั้น


4

ดังนั้นอย่าทำอย่างนั้น!

คุณมีสองทางเลือก:

  1. เรียกใช้ตัวเลือกเพื่อรับ COUNT ชุดผลลัพธ์ของคุณและดึงเฉพาะข้อมูลหากจำนวนนี้ไม่ใช่ศูนย์ หรือ

  2. ใช้แบบสอบถามชนิดอื่น (ที่ได้รับชุดผลลัพธ์) และตรวจสอบว่ามีผลลัพธ์ตั้งแต่ 0 รายการขึ้นไป ควรมี 1 ดังนั้นดึงออกมาจากการรวบรวมผลลัพธ์ของคุณและคุณทำเสร็จแล้ว

ฉันจะไปกับข้อเสนอแนะที่สองในข้อตกลงกับ Cletus มันให้ประสิทธิภาพที่ดีกว่าแบบสอบถาม (อาจ) 2 ยังทำงานน้อยลง


1
ตัวเลือก 3 ลอง / จับ NoResultException
Ced

3

รวมบิตที่มีประโยชน์ของคำตอบที่มีอยู่ (จำกัด จำนวนผลลัพธ์ตรวจสอบว่าผลลัพธ์นั้นไม่ซ้ำกัน) และใช้ชื่อเมธอด estabilshed (ไฮเบอร์เนต) เราได้รับ:

/**
 * 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

เมธอดที่ไม่มีเอกสารuniqueResultOptionalใน org.hibernate.query.Query ควรทำการหลอกลวง แทนที่จะต้องจับคุณก็สามารถโทรNoResultExceptionquery.uniqueResultOptional().orElse(null)



1

นี่คือตรรกะเดียวกับที่คนอื่นแนะนำ (รับ resultList คืนองค์ประกอบหรือโมฆะเท่านั้น) โดยใช้ Google Guava และ TypedQuery

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

โปรดทราบว่า Guava จะส่งคืน IllegalArgumentException ที่ไม่ได้ใช้งานหากชุดผลลัพธ์มีผลลัพธ์มากกว่าหนึ่งรายการ (ข้อยกเว้นเหมาะสมกับลูกค้าของ getOnlyElement () เนื่องจากใช้รายการผลลัพธ์เป็นอาร์กิวเมนต์ แต่ไม่เข้าใจลูกค้าของ getSingleResultOrNull () น้อยกว่า)


1

นี่คือส่วนขยายอื่นเวลานี้ใน Scala

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

ด้วยแมงดานี้:

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

ดูรหัสนี้:

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

เมื่อไหร่ findFirst()ถูกเรียกอาจจะสามารถโยน NullPointerException

aproach ที่ดีที่สุดคือ:

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


0

ดังนั้นวิธีแก้ปัญหา "พยายามเขียนซ้ำโดยไม่มีข้อยกเว้น" ในหน้านี้จึงมีปัญหาเล็กน้อย ไม่ว่าจะเป็นการโยนข้อยกเว้น NonUnique หรือการโยนในบางกรณีที่ไม่ถูกต้องเช่นกัน (ดูด้านล่าง)

ฉันคิดว่าทางออกที่เหมาะสมคือ (อาจ) สิ่งนี้:

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

มันจะส่งคืนด้วย null หากมีองค์ประกอบ 0 รายการในรายการส่งคืน nonunique หากมีองค์ประกอบต่าง ๆ ในรายการ แต่จะไม่ส่งคืน nonunique เมื่อหนึ่งในตัวเลือกของคุณไม่ได้รับการออกแบบอย่างเหมาะสมและส่งคืนวัตถุเดียวกันมากกว่าหนึ่งครั้ง

รู้สึกอิสระที่จะแสดงความคิดเห็น


0

ฉันทำได้โดยได้รับรายการผลลัพธ์จากนั้นตรวจสอบว่าว่างเปล่าหรือไม่

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

มันน่ารำคาญมากที่getSingleResult()พ่นข้อยกเว้น

โยน:

  1. NoResultException - หากไม่มีผลลัพธ์
  2. NonUniqueResultException - หากมีมากกว่าหนึ่งผลลัพธ์และข้อยกเว้นอื่น ๆ ที่คุณสามารถรับข้อมูลเพิ่มเติมได้จากเอกสารของพวกเขา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.