Saat menggunakan getOne dan temukan metode One Spring Data JPA

154

Saya memiliki kasus penggunaan yang disebut sebagai berikut:

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
    return this.userControlRepository.getOne(id);
}

Amati Propagation yang@Transactional dimiliki.REQUIRES_NEW dan repositori menggunakan getOne . Ketika saya menjalankan aplikasi, saya menerima pesan kesalahan berikut:

Exception in thread "main" org.hibernate.LazyInitializationException: 
could not initialize proxy - no Session
...

Tetapi jika saya mengubah getOne(id)dengan findOne(id)semua bekerja dengan baik.

BTW, tepat sebelum use case memanggil metode getUserControlById , sudah disebut metode insertUserControl

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
    return this.userControlRepository.save(userControl);
}

Kedua metode adalah Propagation.REQUIRES_NEW karena saya melakukan kontrol audit yang sederhana .

Saya menggunakan getOnemetode ini karena didefinisikan dalam antarmuka JpaRepository dan antarmuka Repositori saya memanjang dari sana, saya bekerja dengan JPA tentu saja.

The JpaRepository antarmuka meluas dari CrudRepository . The findOne(id)metode didefinisikan dalam CrudRepository.

Pertanyaan saya adalah:

  1. Mengapa gagal getOne(id)metode ini?
  2. Kapan saya harus menggunakan getOne(id)metode ini?

Saya bekerja dengan repositori lain dan semua menggunakan getOne(id)metode dan semua berfungsi dengan baik, hanya ketika saya menggunakan Propagasi . REQUIRES_NEW gagal.

Menurut dengan getOne API:

Mengembalikan referensi ke entitas dengan pengidentifikasi yang diberikan.

Menurut dengan findOne API:

Mengambil entitas dengan idnya.

3) Kapan saya harus menggunakan findOne(id)metode ini?

4) Metode apa yang direkomendasikan untuk digunakan?

Terima kasih sebelumnya.

Manuel Jordan
sumber
Anda sebaiknya tidak menggunakan getOne () untuk menguji keberadaan objek dalam basis data, karena dengan getOne Anda selalu mendapatkan objek! = Null, sedangkan findOne memberikan null.
Uwe Allner

Jawaban:

137

TL; DR

T findOne(ID id)(nama di API lama) / Optional<T> findById(ID id)(nama di API baru) bergantung pada EntityManager.find()yang melakukan pemuatan entitas yang bersemangat .

T getOne(ID id)bergantung pada EntityManager.getReference()yang melakukan pemuatan entitas malas . Jadi untuk memastikan pemuatan entitas yang efektif, diperlukan metode untuk itu.

findOne()/findById()benar-benar lebih jelas dan mudah digunakan daripada getOne().
Jadi dalam sangat sebagian besar kasus, mendukung findOne()/findById()lebih getOne().


Perubahan API

Setidaknya dari 2.0versi, Spring-Data-Jpadimodifikasi findOne().
Sebelumnya, itu didefinisikan dalam CrudRepositoryantarmuka sebagai:

T findOne(ID primaryKey);

Sekarang, findOne()metode tunggal yang akan Anda temukan CrudRepositoryadalah yang didefinisikan dalam QueryByExampleExecutorantarmuka sebagai:

<S extends T> Optional<S> findOne(Example<S> example);

Yang akhirnya diimplementasikan oleh SimpleJpaRepository, implementasi standar CrudRepositoryantarmuka.
Metode ini adalah kueri dengan pencarian contoh dan Anda tidak ingin itu sebagai pengganti.

Faktanya, metode dengan perilaku yang sama masih ada di API baru tetapi nama metode telah berubah.
Itu diubah namanya dari findOne()menjadi findById()di CrudRepositoryantarmuka:

Optional<T> findById(ID id); 

Sekarang mengembalikan sebuah Optional. Yang tidak begitu buruk untuk dicegah NullPointerException.

Jadi, pilihan sebenarnya sekarang antara Optional<T> findById(ID id)dan T getOne(ID id).


Dua metode berbeda yang mengandalkan dua metode pengambilan EntityManager JPA yang berbeda

1) Optional<T> findById(ID id)Javadoc menyatakan bahwa:

Mengambil entitas dengan idnya.

Ketika kita melihat implementasi, kita dapat melihat bahwa itu bergantung EntityManager.find()untuk melakukan pengambilan:

public Optional<T> findById(ID id) {

    Assert.notNull(id, ID_MUST_NOT_BE_NULL);

    Class<T> domainType = getDomainClass();

    if (metadata == null) {
        return Optional.ofNullable(em.find(domainType, id));
    }

    LockModeType type = metadata.getLockModeType();

    Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

    return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}

Dan di sini em.find()adalah sebuah EntityManagermetode dinyatakan sebagai:

public <T> T find(Class<T> entityClass, Object primaryKey,
                  Map<String, Object> properties);

Status javadoc-nya:

Temukan dengan kunci utama, menggunakan properti yang ditentukan

Jadi, mengambil entitas yang dimuat tampaknya diharapkan.

2) Sementara T getOne(ID id)javadoc menyatakan (penekanan adalah milikku):

Mengembalikan referensi ke entitas dengan pengidentifikasi yang diberikan.

Faktanya, terminologi rujukan benar-benar papan dan JPA API tidak menentukan getOne()metode apa pun .
Jadi hal terbaik yang harus dilakukan untuk memahami apa yang dilakukan pembungkus Spring adalah dengan melihat implementasinya:

@Override
public T getOne(ID id) {
    Assert.notNull(id, ID_MUST_NOT_BE_NULL);
    return em.getReference(getDomainClass(), id);
}

Berikut em.getReference()adalah EntityManagermetode yang dinyatakan sebagai:

public <T> T getReference(Class<T> entityClass,
                              Object primaryKey);

Dan untungnya, EntityManagerjavadoc mendefinisikan dengan lebih baik niatnya (penekanan adalah milikku):

Dapatkan contoh, yang keadaannya mungkin diambil dengan malas . Jika instance yang diminta tidak ada dalam database, EntityNotFoundException dilemparkan ketika keadaan instance diakses pertama kali . (Runtime penyedia persistensi diizinkan untuk melempar EntityNotFoundException ketika getReference dipanggil.) Aplikasi tidak boleh berharap bahwa keadaan instance akan tersedia pada saat dilepas , kecuali itu diakses oleh aplikasi ketika manajer entitas terbuka.

Jadi, memohon getOne()dapat mengembalikan entitas yang diambil malas.
Di sini, pengambilan malas tidak merujuk pada hubungan entitas tetapi entitas itu sendiri.

Ini berarti bahwa jika kita memanggil getOne()dan kemudian konteks Persistence ditutup, entitas mungkin tidak pernah dimuat dan hasilnya benar-benar tidak dapat diprediksi.
Misalnya jika objek proxy serial, Anda bisa mendapatkan nullreferensi sebagai hasil serial atau jika metode dipanggil pada objek proxy, pengecualian seperti LazyInitializationExceptiondilemparkan.
Jadi dalam situasi seperti ini, lemparan EntityNotFoundExceptionitu adalah alasan utama yang digunakan getOne()untuk menangani sebuah instance yang tidak ada dalam database karena situasi kesalahan mungkin tidak pernah dilakukan ketika entitas tidak ada.

Bagaimanapun, untuk memastikan pemuatannya, Anda harus memanipulasi entitas saat sesi dibuka. Anda dapat melakukannya dengan menggunakan metode apa pun pada entitas.
Atau penggunaan alternatif yang lebih baik findById(ID id)daripada.


Mengapa API begitu tidak jelas?

Untuk menyelesaikan, dua pertanyaan untuk pengembang Spring-Data-JPA:

  • mengapa tidak memiliki dokumentasi yang lebih jelas getOne()? Entitas pemuatan malas sebenarnya bukan detail.

  • mengapa Anda perlu memperkenalkan getOne()untuk membungkus EM.getReference()?
    Mengapa tidak hanya menempel metode dibungkus: getReference()? Metode EM ini sangat khusus saat getOne() menyampaikan pemrosesan yang sangat sederhana.

davidxxx
sumber
3
Saya bingung mengapa getOne () tidak melempar EntityNotFoundException, tetapi "EntityNotFoundException Anda dilempar ketika keadaan instance diakses pertama kali" menjelaskan konsepnya kepada saya. Terima kasih
TheCoder
Ringkasan jawaban ini: getOne()menggunakan lazy loading, dan melempar EntityNotFoundExceptionjika tidak ada barang yang ditemukan. findById()segera memuat, dan mengembalikan nol jika tidak ditemukan. Karena ada beberapa situasi yang tidak dapat diprediksi dengan getOne (), disarankan menggunakan findById () sebagai gantinya.
Janac Meena
124

Perbedaan mendasar adalah bahwa getOnemalas dimuat dan findOnetidak.

Perhatikan contoh berikut:

public static String NON_EXISTING_ID = -1;
...
MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID);
MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID);

if(findEnt != null) {
     findEnt.getText(); // findEnt is null - this code is not executed
}

if(getEnt != null) {
     getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!!
}
Marek Halmo
sumber
1
tidakkah malas dimuat berarti bahwa itu hanya akan dimuat ketika entitas akan digunakan? jadi saya berharap getEnt menjadi nol dan kode di dalam yang kedua jika tidak dieksekusi Bisakah Anda jelaskan. Terima kasih!
Doug
Jika dibungkus di dalam layanan web CompletableFuture <> saya telah menemukan bahwa Anda ingin menggunakan findOne () vs. getOne () karena implementasinya yang malas.
Fratt
76

1. Mengapa metode getOne (id) gagal?

Lihat bagian ini dalam dokumen . Anda menimpa transaksi yang sudah ada di tempat mungkin menyebabkan masalah. Namun, tanpa info lebih lanjut, yang ini sulit dijawab.

2. Kapan saya harus menggunakan metode getOne (id)?

Tanpa menggali ke dalam internal Spring Data JPA, perbedaannya tampaknya pada mekanisme yang digunakan untuk mengambil entitas.

Jika Anda melihat javadoc untuk getOne(ID)di bawah Lihat Juga :

See Also:
EntityManager.getReference(Class, Object)

tampaknya metode ini hanya mendelegasikan ke implementasi manajer entitas JPA.

Namun, dokumen untuk findOne(ID)tidak menyebutkan ini.

Petunjuknya juga atas nama repositori. JpaRepositorykhusus untuk JPA dan karenanya dapat mendelegasikan panggilan ke manajer entitas jika diperlukan. CrudRepositoryadalah agnostik dari teknologi ketekunan yang digunakan. Lihat di sini . Ini digunakan sebagai antarmuka penanda untuk beberapa teknologi persistensi seperti JPA, Neo4J dll.

Jadi sebenarnya tidak ada 'perbedaan' dalam dua metode untuk kasus penggunaan Anda, hanya saja findOne(ID)lebih umum daripada yang lebih khusus getOne(ID). Yang mana yang Anda gunakan terserah Anda dan proyek Anda, tetapi saya pribadi akan tetap menggunakannya findOne(ID)karena membuat kode Anda kurang spesifik implementasi dan membuka pintu untuk pindah ke hal-hal seperti MongoDB dll di masa depan tanpa terlalu banyak refactoring :)

Donovan Muller
sumber
Terima kasih, Donovan, sudah merasakan jawaban Anda.
Manuel Jordan
20
Saya pikir itu sangat menyesatkan untuk mengatakan bahwa di there's not really a 'difference' in the two methodssini, karena memang ada perbedaan besar dalam bagaimana entitas diambil dan apa yang Anda harapkan metode untuk kembali. Jawaban lebih jauh ke bawah oleh @davidxxx menyoroti ini dengan sangat baik, dan saya pikir semua orang yang menggunakan Spring Data JPA harus mengetahui hal ini. Kalau tidak, itu bisa menyebabkan sakit kepala yang cukup.
fridberg
16

The getOnemetode kembali hanya referensi dari DB (lazy loading). Jadi pada dasarnya Anda berada di luar transaksi ( TransactionalAnda telah menyatakan dalam kelas layanan tidak dipertimbangkan), dan kesalahan terjadi.

Bogdan Mata
sumber
Tampaknya EntityManager.getReference (Class, Object) mengembalikan "tidak ada" karena kita berada dalam ruang lingkup Transaksi baru.
Manuel Jordan
2

Saya benar-benar merasa sangat sulit dari jawaban di atas. Dari perspektif debugging saya hampir menghabiskan 8 jam untuk mengetahui kesalahan konyol.

Saya telah menguji pegas + hibernate + dozer + proyek Mysql. Agar jelas.

Saya memiliki entitas Pengguna, Entitas Buku. Anda melakukan perhitungan pemetaan.

Apakah Banyak Buku terikat dengan Satu pengguna. Tetapi dalam UserServiceImpl saya mencoba menemukannya dengan getOne (userId);

public UserDTO getById(int userId) throws Exception {

    final User user = userDao.getOne(userId);

    if (user == null) {
        throw new ServiceException("User not found", HttpStatus.NOT_FOUND);
    }
    userDto = mapEntityToDto.transformBO(user, UserDTO.class);

    return userDto;
}

Hasil sisanya adalah

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 1,
        "name": "TEST_ME",
        "bookList": null
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}

Kode di atas tidak mengambil buku yang dibaca oleh pengguna, katakan saja.

BookList selalu nol karena getOne (ID). Setelah mengubah ke findOne (ID). Hasilnya adalah

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 0,
        "name": "Annama",
        "bookList": [
            {
                "id": 2,
                "book_no": "The karma of searching",
            }
        ]
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}

EngineSense
sumber
-1

sementara spring.jpa.open-in-view benar, saya tidak punya masalah dengan getOne tapi setelah pengaturannya menjadi false, saya mendapat LazyInitializationException. Kemudian masalah diselesaikan dengan mengganti dengan findById.
Meskipun ada solusi lain tanpa mengganti metode getOne, dan itu adalah meletakkan @Transactional pada metode yang memanggil repository.getOne (id). Dengan cara ini transaksi akan ada dan sesi tidak akan ditutup dalam metode Anda dan saat menggunakan entitas tidak akan ada LazyInitializationException.

Farshad Falaki
sumber
-2

Saya memiliki masalah yang sama memahami mengapa JpaRespository.getOne (id) tidak berfungsi dan melempar kesalahan.

Saya pergi dan berganti ke JpaRespository.findById (id) yang mengharuskan Anda mengembalikan Opsional.

Ini mungkin komentar pertama saya di StackOverflow.

akshaymittal143
sumber
Sayangnya, ini tidak memberikan dan menjawab pertanyaan, juga tidak meningkatkan jawaban yang ada.
JSTL
Begitu ya, tidak masalah.
akshaymittal143