GetOne ve findOne yöntemlerini kullanırken Spring Data JPA


154

Aşağıdakileri çağırdığı bir kullanım durumum var:

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

Gözlemleyin @Transactionalsahiptir Propagation.REQUIRES_NEW ve depo kullanır getOne . Uygulamayı çalıştırdığımda, aşağıdaki hata iletisini alıyorum:

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

Ama değiştirirseniz getOne(id)tarafından findOne(id)tüm eserler cezası.

Kullanım durumu çağırır hemen önce BTW, getUserControlById yöntemi, zaten çağırdı insertUserControl yöntemi

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

Her iki yöntem de Yayılım.REQUIRES_NEW çünkü basit bir denetim denetimi yapıyorum .

JpaRepository arabiriminde getOnetanımlandığı ve Repository arabirim oradan uzandığı için yöntemi kullanıyorum, elbette JPA ile çalışıyorum.

JpaRepository arayüz uzanır CrudRepository . findOne(id)Yöntem tanımlanmıştır CrudRepository.

Sorularım:

  1. getOne(id)Yöntem neden başarısız oluyor ?
  2. getOne(id)Yöntemi ne zaman kullanmalıyım ?

Diğer havuzlarla çalışıyorum ve tüm getOne(id)yöntemi kullanıyorum ve hepsi iyi çalışıyor, sadece Propagation.REQUIRES_NEW kullandığımda başarısız oluyor.

İle göre getOne API:

Belirtilen tanımlayıcıya sahip varlığa bir başvuru döndürür.

İle göre findOne API:

Bir varlığı kimliğiyle alır.

3) findOne(id)Yöntemi ne zaman kullanmalıyım ?

4) Hangi yöntemin kullanılması önerilir?

Şimdiden teşekkürler.


Veri tabanındaki bir nesnenin varlığını test etmek için özellikle getOne () kullanmamalısınız, çünkü getOne ile her zaman bir nesne alırsınız = null, findOne null verir.
Uwe Allner

Yanıtlar:


137

TL; DR

T findOne(ID id)(eski API'deki Optional<T> findById(ID id)ad ) / (yeni API'daki ad), EntityManager.find()bir varlığın istekli yüklemesini gerçekleştirdiğine dayanır .

T getOne(ID id)EntityManager.getReference()bir varlık tembel yükleme gerçekleştirir dayanır . Bu nedenle, işletmenin etkin bir şekilde yüklenmesini sağlamak için, üzerine bir yöntem çağırılması gerekir.

findOne()/findById()gerçekten daha net ve kullanımı kolaydır getOne().
Yani vakaların çok çoğunda, iyilik findOne()/findById()üzerine getOne().


API Değişikliği

En azından 2.0sürüm Spring-Data-Jpadeğiştirildi findOne().
Önceden, CrudRepositoryarayüzde şu şekilde tanımlanmıştı :

T findOne(ID primaryKey);

Şimdi, findOne()bulacağınız tek yöntem CrudRepository, QueryByExampleExecutorarayüzde hangi yöntemin tanımlandığıdır :

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

Bu, nihayet arabirimin SimpleJpaRepositoryvarsayılan uygulamasıyla uygulanır CrudRepository.
Bu yöntem, örnek arama ile yapılan bir sorgudur ve bunun yerine yedek olarak istemezsiniz.

Aslında, aynı davranışa sahip yöntem hala yeni API'dadır, ancak yöntem adı değişmiştir.
Bu yeniden adlandırıldı findOne()için findById()de CrudRepositorybir arayüz:

Optional<T> findById(ID id); 

Şimdi bir Optional. Önlemek o kadar da kötü değil NullPointerException.

Yani, asıl seçim şimdi Optional<T> findById(ID id)ve arasında T getOne(ID id).


İki farklı JPA EntityManager alma yöntemine dayanan iki farklı yöntem

1) Optional<T> findById(ID id)Javadoc şunları söylüyor:

Bir varlığı kimliğiyle alır.

Uygulamaya baktığımızda EntityManager.find(), geri alma işlemine dayandığını görebiliriz :

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

Ve işte em.find()şöyle EntityManagerbildirilen bir yöntem:

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

Javadoc şöyle diyor:

Belirtilen özellikleri kullanarak birincil anahtarla bulma

Bu nedenle, yüklü bir varlığın geri alınması bekleniyor.

2) T getOne(ID id)Javadoc belirtirken (vurgu benimdir):

Belirtilen tanımlayıcıya sahip varlığa bir başvuru döndürür .

Aslında, referans terminolojisi gerçekten tahtadır ve JPA API herhangi bir getOne()yöntem belirtmez .
Yani Bahar sarıcısının ne yaptığını anlamak için yapılacak en iyi şey uygulamaya bakmaktır:

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

İşte em.getReference()olarak EntityManagerbildirilen bir yöntem:

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

Ve neyse ki, EntityManagerjavadoc niyetini daha iyi tanımladı (vurgu benimdir):

Durumu tembel bir şekilde getirilebilecek bir örnek alın . İstenen örnek veritabanında yoksa , örnek durumuna ilk kez erişildiğinde EntityNotFoundException oluşturulur . (Kalıcılık sağlayıcı çalışma zamanının, getReference çağrıldığında EntityNotFoundException özel durumunu atmasına izin verilir.) Uygulama, varlık yöneticisi açıkken uygulama tarafından erişilmedikçe , örnek durumunun ayrılma üzerine kullanılabilir olmasını beklememelidir .

Bu nedenle, çağırmak getOne()tembel olarak getirilmiş bir varlığı döndürebilir.
Burada tembel getirme, varlığın ilişkilerini değil, varlığın kendisini ifade eder.

Bu, eğer çağırırsak getOne()ve sonra Kalıcılık bağlamı kapanırsa, varlığın asla yüklenemeyeceği ve dolayısıyla sonucun gerçekten öngörülemez olduğu anlamına gelir.
Örneğin, proxy nesnesi serileştirilmişse, nullserileştirilmiş sonuç olarak bir başvuru alabilirsiniz veya proxy nesnesinde bir yöntem çağrılırsa, böyle bir istisna LazyInitializationExceptionatılır.
Dolayısıyla bu tür bir durumda, bunun bir hata durumu olarak veritabanında bulunmayan bir örneği işlemek EntityNotFoundExceptioniçin kullanılmasının ana nedeni getOne(), varlık mevcut olmadığında asla gerçekleştirilemez.

Her durumda, yüklenmesini sağlamak için oturumu açarken varlığı değiştirmelisiniz. Varlık üzerindeki herhangi bir yöntemi çağırarak bunu yapabilirsiniz.
Veya findById(ID id)yerine daha iyi bir alternatif kullanım .


Neden bu kadar belirsiz bir API?

Bitirmek için, Spring-Data-JPA geliştiricileri için iki soru:

  • neden daha açık bir belgeye sahip değilsiniz getOne()? Varlık tembel yükleme gerçekten bir ayrıntı değil.

  • neden getOne()sarmak için tanıtmak gerekiyor EM.getReference()?
    Neden sadece sarılı yönteme bağlı kalmıyorsunuz getReference()? Bu EM yöntemi gerçekten çok getOne() özelken çok basit bir işlemdir.


3
Neden getOne () EntityNotFoundException atıyor karıştı, ama "örnek durumuna ilk erişildiğinde EntityNotFoundException atılır" kavramı bana açıkladı. Teşekkürler
TheCoder

Bu cevabın özeti: getOne()tembel yüklemeyi kullanır ve bir EntityNotFoundExceptionöğe bulunmazsa atar . findById()hemen yüklenir ve bulunamazsa null değerini döndürür. GetOne () ile bazı öngörülemeyen durumlar olduğundan, bunun yerine findById () kullanılması önerilir.
Janac Meena

124

Temel fark, getOnetembel yüklü olması ve findOneolmamasıdır.

Aşağıdaki örneği düşünün:

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

1
tembel yüklü değil, yalnızca varlık kullanılacaksa yükleneceği anlamına mı gelir? Bu yüzden getEnt null olmasını ve yürütülecek değilse ikinci içindeki kod beklenir Lütfen açıklayabilir misiniz. Teşekkürler!
Doug

Bir CompletableFuture <> web hizmeti içine sarılmışsa, tembel uygulaması nedeniyle findOne () vs. getOne () kullanmak isteyeceksiniz.
Fratt

76

1. getOne (id) yöntemi neden başarısız oluyor?

Belgelerdeki bu bölüme bakın . Zaten yerinde olan işlemi geçersiz kılmanız soruna neden oluyor olabilir. Ancak, daha fazla bilgi olmadan buna cevap vermek zordur.

2. getOne (id) yöntemini ne zaman kullanmalıyım?

Spring Data JPA'nın içlerine girmeden fark, varlığı almak için kullanılan mekanizmada görünmektedir.

Eğer bakarsak JavaDoc'u için getOne(ID)altında Ayrıca Bkz :

See Also:
EntityManager.getReference(Class, Object)

bu yöntemin sadece JPA varlık yöneticisinin uygulanmasına delege ettiği görülüyor.

Ancak dokümanlar için findOne(ID)bu söz etmeyin.

İpucu ayrıca depoların adlarındadır. JpaRepositoryJPA'ya özgüdür ve bu nedenle gerektiğinde çağrıları varlık yöneticisine devredebilir. CrudRepositorykullanılan kalıcılık teknolojisinden bağımsızdır. Buraya bak . JPA, Neo4J gibi çoklu kalıcılık teknolojileri için bir işaretleyici arayüzü olarak kullanılır .

Bu nedenle, kullanım durumlarınız için iki yöntemde gerçekten bir 'fark' yoktur, sadece findOne(ID)daha uzman olandan daha geneldir getOne(ID). Hangisini kullanacağınız size ve projenize bağlı, ancak findOne(ID)kodunuzu daha az uygulamaya özgü kılan ve gelecekte çok fazla yeniden düzenleme yapmadan MongoDB vb.


Teşekkür ederim Donovan, cevabınızı hissediyor.
Manuel Jordan

20
Sanırım there's not really a 'difference' in the two methodsburada bunu söylemek çok yanıltıcı , çünkü varlığın nasıl geri alındığı ve yöntemin geri dönmesini beklemeniz gereken şeylerde gerçekten büyük bir fark var. @Davidxxx tarafından verilen yanıt bunu çok iyi vurgular ve bence Spring Data JPA kullanan herkes bunun farkında olmalıdır. Aksi takdirde, biraz baş ağrısına neden olabilir.
fridberg

16

getOneYöntem döner DB tek referans (geç yükleme). Temel olarak işlemin Transactionaldışındasınız (servis sınıfında beyan edilmiş sayılmazsınız) ve hata oluşur.


Yeni bir İşlem kapsamında olduğumuz için EntityManager.getReference (Class, Object) "hiçbir şey" döndürmez.
Manuel Jordan

2

Yukarıdaki cevaplardan gerçekten çok zorlanıyorum. Hata ayıklama perspektifinden aptalca hatayı bilmek için neredeyse 8 saat geçirdim.

Test baharı + hazırda bekleme + dozer + Mysql projem var. Açık olmak gerekirse.

Kullanıcı varlığım, Kitap Varlığım var. Harita hesaplamaları yaparsınız.

Birden çok kitap Bir kullanıcıya bağlı mıydı? Ancak UserServiceImpl i getOne (userId) tarafından bulmaya çalışıyordu;

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

Geri kalan sonuç

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

}

Yukarıdaki kod kullanıcı tarafından okunan kitapları getirmedi diyelim.

GetLo (ID) nedeniyle bookList her zaman boştu. FindOne (ID) olarak değiştirdikten sonra. Sonuç

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

}


-1

spring.jpa.open-in-view doğruyken, getOne ile ilgili herhangi bir sorunum yoktu, ancak false olarak ayarladıktan sonra LazyInitializationException var. Daha sonra problem, findById ile değiştirilerek çözüldü.
GetOne yöntemini değiştirmeden başka bir çözüm olsa da, bu repository.getOne (id) çağıran yönteme @Transactional olarak konur. Bu şekilde işlem yapılır ve oturum yönteminizde kapatılmaz ve varlık kullanılırken herhangi bir LazyInitializationException olmaz.


-2

Neden JpaRespository.getOne (id) çalışmıyor ve bir hata atmak anlamak benzer bir sorun vardı.

Gittim ve isteğe bağlı döndürmenizi gerektiren JpaRespository.findById (id) değiştirin.

Bu muhtemelen StackOverflow hakkındaki ilk yorumum.


Ne yazık ki, bu soruya cevap vermiyor ve mevcut cevapları geliştirmiyor.
JSTL

Anlıyorum, sorun değil.
akshaymittal143
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.