JPA getSingleResult () atau null

138

Saya memiliki insertOrUpdatemetode yang memasukkan Entityjika tidak ada atau memperbaruinya jika ada. Untuk mengaktifkan ini, saya harus findByIdAndForeignKey, jika dikembalikan nullmasukkan jika tidak kemudian perbarui. Masalahnya adalah bagaimana cara memeriksa apakah itu ada? Jadi saya mencoba getSingleResult. Tapi itu membuat pengecualian jika

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

tapi getSingleResultmelempar Exception.

Terima kasih

Eugene Ramirez
sumber

Jawaban:

269

Melempar pengecualian adalah cara getSingleResult()menandakan itu tidak dapat ditemukan. Secara pribadi saya tidak tahan dengan API semacam ini. Ini memaksa penanganan pengecualian palsu tanpa manfaat nyata. Anda hanya perlu membungkus kode dalam blok coba-tangkap.

Alternatifnya Anda bisa membuat kueri untuk daftar dan melihat apakah itu kosong. Itu tidak menimbulkan pengecualian. Sebenarnya karena Anda tidak melakukan pencarian kunci primer secara teknis, mungkin ada beberapa hasil (meskipun satu, keduanya atau kombinasi kunci asing atau batasan membuat hal ini tidak mungkin dalam praktiknya) jadi ini mungkin solusi yang lebih tepat.

cletus
sumber
118
Saya tidak setuju, getSingleResult()digunakan dalam situasi seperti: " Saya sangat yakin bahwa rekaman ini ada. Tembak saya jika tidak ". Saya tidak ingin menguji nullsetiap kali saya menggunakan metode ini karena saya yakin itu tidak akan mengembalikannya. Jika tidak, itu menyebabkan banyak pemrograman boilerplate dan defensif. Dan jika record benar-benar tidak ada (berlawanan dengan apa yang kita asumsikan), akan lebih baik NoResultExceptionjika dibandingkan dengan NullPointerExceptionbeberapa baris kemudian. Tentu saja memiliki dua versi getSingleResult()akan luar biasa, tetapi jika saya harus mengambil satu ...
Tomasz Nurkiewicz
8
@cletus Null memang merupakan nilai pengembalian yang valid untuk database.
Bill Rosmus
13
@Tomasurdianto itu poin yang bagus. Namun, sepertinya harus ada beberapa jenis "getSingleResultOrNull". Saya kira Anda bisa membuat pembungkus untuk itu.
cbmeeks
3
Berikut adalah beberapa informasi tentang manfaat pengecualian mulai dilempar dari getSingleResult (): Queries dapat digunakan untuk mengambil hampir semua hal termasuk nilai satu kolom dalam satu baris. Jika getSingleResult () akan mengembalikan null, Anda tidak dapat mengetahui apakah kueri tidak cocok dengan baris mana pun atau apakah kueri cocok dengan satu baris tetapi kolom yang dipilih berisi null sebagai nilainya. dari: stackoverflow.com/a/12155901/1242321
pengguna1242321
5
Ini harus mengembalikan Opsional <T>. Itu adalah cara yang baik untuk menunjukkan nilai yang hilang.
Vivek Kothari
34

Saya merangkum logika dalam metode pembantu berikut.

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();
    }
}
Eugene Katz
sumber
2
Perhatikan bahwa Anda bisa menjadi sedikit lebih optimal dengan memanggil Query.setMaxResults (1). Sayangnya, karena Query bersifat stateful, Anda ingin menangkap nilai Query.getMaxResults () dan memperbaiki objek dalam blok coba-akhirnya, dan mungkin gagal sama sekali jika Query.getFirstResult () mengembalikan sesuatu yang menarik.
Patrick Linskey
begitulah cara kami menerapkannya di proyek kami. Tidak pernah ada masalah apa pun dengan penerapan ini
walv
30

Coba ini di Java 8:

Optional first = query.getResultList().stream().findFirst();
Impala67
sumber
4
Anda dapat menyingkirkan Opsional dengan menambahkan.orElse(null)
Justin Rowe
24

Berikut opsi yang bagus untuk melakukan ini:

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);
}
Rodrigo IronMan
sumber
2
Rapi! Saya akan menerima TypedQuery<T>meskipun, dalam hal getResultList()ini maka sudah diketik dengan benar sebagai file List<T>.
Rup
Dalam kombinasi dengan fetch()entitas mungkin tidak terisi seluruhnya. Lihat stackoverflow.com/a/39235828/661414
Leukipp
1
Ini adalah pendekatan yang sangat bagus. Catatan yang setMaxResults()memiliki antarmuka yang lancar sehingga Anda dapat menulis query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Ini harus menjadi skema panggilan paling efisien di Java 8+.
Dirk Hillbrecht
17

Spring memiliki metode utilitas untuk ini:

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

Saya telah selesai (di Java 8):

query.getResultList().stream().findFirst().orElse(null);
Zhurov Konstantin
sumber
apa yang Anda maksud dengan kueri?
Enrico Giurin
Maksud Anda HibernateQuery? Bagaimana jika saya ingin menggunakan api JPA murni? Tidak ada metode seperti itu di javax.persistence.Query
Enrico Giurin
2
@EnricoGiurin, saya telah mengedit cuplikannya. Bekerja dengan baik. Tidak ada uji coba, dan tidak ada pemeriksaan list.size. Solusi satu liner terbaik.
LovaBill
10

Dari JPA 2.2 , alih-alih .getResultList()dan memeriksa apakah daftar kosong atau membuat aliran, Anda dapat mengembalikan aliran dan mengambil elemen pertama.

.getResultStream()
.findFirst()
.orElse(null);
Serafin
sumber
7

Jika Anda ingin menggunakan mekanisme coba / tangkap untuk menangani masalah ini .. maka dapat digunakan untuk bertindak seperti if / else. Saya menggunakan coba / tangkap untuk menambahkan rekor baru ketika saya tidak menemukan yang sudah ada.

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); 
}
Tukang sortir
sumber
6

Berikut adalah versi yang diketik / generik, berdasarkan implementasi 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);
}
Emmanuel Touzery
sumber
5

Ada alternatif lain yang saya rekomendasikan:

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

Pengamanan ini terhadap Pengecualian Null Pointer, menjamin hanya 1 hasil yang dikembalikan.

ace.
sumber
4

Jadi jangan lakukan itu!

Anda memiliki dua opsi:

  1. Jalankan pilihan untuk mendapatkan JUMLAH kumpulan hasil Anda, dan hanya menarik data jika hitungan ini bukan nol; atau

  2. Gunakan jenis kueri lain (yang mendapatkan kumpulan hasil) dan periksa apakah memiliki 0 hasil atau lebih. Ini harus memiliki 1, jadi keluarkan dari koleksi hasil Anda dan Anda selesai.

Saya setuju dengan saran kedua, sesuai dengan Cletus. Ini memberikan kinerja yang lebih baik daripada (berpotensi) 2 kueri. Juga lebih sedikit pekerjaan.

Carl Smotricz
sumber
1
Opsi 3 Coba / tangkap NoResultException
Ced
3

Menggabungkan bit-bit berguna dari jawaban yang ada (membatasi jumlah hasil, memeriksa bahwa hasilnya unik) dan menggunakan nama metode estabilshed (Hibernate), kita mendapatkan:

/**
 * 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);
}
Peter Walser
sumber
3

Metode tidak berdokumen uniqueResultOptionaldi org.hibernate.query.Query harus melakukan triknya. Alih-alih harus menangkap, NoResultExceptionAnda bisa menelepon query.uniqueResultOptional().orElse(null).

at_sof
sumber
2

Saya menyelesaikan ini dengan menggunakan List<?> myList = query.getResultList();dan memeriksa apakah myList.size()sama dengan nol.

Сергій Катрюк
sumber
1

Berikut logika yang sama seperti yang disarankan orang lain (dapatkan resultList, kembalikan satu-satunya elemen atau null), menggunakan Google Guava dan TypedQuery.

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

Perhatikan bahwa Guava akan mengembalikan IllegalArgumentException yang tidak intuitif jika rangkaian hasil memiliki lebih dari satu hasil. (Pengecualian masuk akal bagi klien getOnlyElement (), karena ini mengambil daftar hasil sebagai argumennya, tetapi kurang dapat dimengerti oleh klien getSingleResultOrNull ().)

tpdi
sumber
1

Berikut ekstensi lainnya, kali ini di Scala.

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

Dengan germo ini:

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)
}
Pete Montgomery
sumber
1

Jadi semua solusi "coba tulis ulang tanpa pengecualian" di halaman ini memiliki masalah kecil. Entah itu tidak memunculkan pengecualian NonUnique, juga tidak melemparkannya dalam beberapa kasus yang salah (lihat di bawah).

Saya pikir solusi yang tepat adalah (mungkin) ini:

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

Ini mengembalikan dengan null jika ada 0 elemen dalam daftar, mengembalikan nonunique jika ada elemen berbeda dalam daftar, tetapi tidak mengembalikan nonunique ketika salah satu pilihan Anda tidak dirancang dengan benar dan mengembalikan objek yang sama lebih dari satu kali.

Jangan ragu untuk berkomentar.

tg44
sumber
Alhamdulillah seseorang menunjukkan kebenaran yang jelas: jika OP memanggil getSingleResult () dia mengharapkan hasilnya unik, bukan hanya mendapatkan yang kebetulan menjadi yang pertama dalam kueri (mungkin tidak berurutan)! Dengan Java8 bahkan lebih bersih:getResultList().stream().distinct().reduce((a, b) -> {throw new NonUniqueResultException();}).orElse(null);
Ilario
1

Lihat kode ini:

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

Saat findFirst()dipanggil mungkin bisa melontarkan NullPointerException.

pendekatan terbaik adalah:

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

Leandro Ferreira
sumber
0

Saya mencapai ini dengan mendapatkan daftar hasil kemudian memeriksa apakah itu kosong

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

Itu sangat menjengkelkan yang getSingleResult()melempar pengecualian

Melempar:

  1. NoResultException - jika tidak ada hasil
  2. NonUniqueResultException - jika lebih dari satu hasil dan beberapa pengecualian lain yang bisa Anda dapatkan lebih banyak info dari dokumentasinya
Uchephilz
sumber
-3

Itu berhasil bagi saya:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
peterzinho 16
sumber