Kratki vodič za hibernaciju svojstvo enable_lazy_load_no_trans
1. Pregled
Dok koristimo lijeno učitavanje u hibernaciji, mogli bismo se suočiti s iznimkama, rekavši da nema sesije.
U ovom uputstvu razgovarat ćemo o rješavanju ovih lijenih problema s učitavanjem. Da bismo to učinili, upotrijebit ćemo Spring Boot da istražimo primjer.
2. Problemi s lijenim učitavanjem
Cilj lijenog učitavanja je ušteda resursa ne učitavanjem povezanih objekata u memoriju kada učitavamo glavni objekt. Umjesto toga, odgađamo inicijalizaciju lijenih entiteta do trenutka kada su potrebni. Hibernate koristi proxyje i omote za prikupljanje kako bi primijenio lijeno učitavanje.
Prilikom dohvaćanja lijeno učitanih podataka u postupku su dva koraka. Prvo, tu je popunjavanje glavnog objekta, i drugo, preuzimanje podataka unutar njegovih proxyja. Za učitavanje podataka uvijek je potrebno otvoreno Sjednica u hibernaciji.
Problem nastaje kada se drugi korak dogodi nakon zatvaranja transakcije, što dovodi do a LazyInitializationException.
Preporučeni pristup je dizajniranje naše aplikacije kako bi se osiguralo da se preuzimanje podataka događa u jednoj transakciji. Ali, to ponekad može biti teško kada koristite lijeni entitet u drugom dijelu koda koji nije u stanju utvrditi što je ili nije učitano.
Hibernate ima zaobilazno rješenje, enable_lazy_load_no_trans imovine. Uključivanje ovoga znači ono svaki dohvat lijenog entiteta otvorit će privremenu sesiju i pokrenuti unutar zasebne transakcije.
3. Primjer lijenog učitavanja
Pogledajmo ponašanje lijenog utovara u nekoliko scenarija.
3.1 Postavljanje entiteta i usluga
Pretpostavimo da imamo dva entiteta, Korisnik i Dokument. Jedan Korisnik mogu imati mnogo Dokuments, i mi ćemo koristiti @OneToMany za opisivanje te veze. Također, mi ćemo koristiti @Fetch (FetchMode.SUBSELECT) radi učinkovitosti.
Trebali bismo napomenuti da, prema zadanim postavkama, @OneToMany ima lijeni tip dohvaćanja.
Ajmo sada definirati naše Korisnik entitet:
Korisnik javne klase @Entity {// ostala polja izostavljena su zbog kratkoće @OneToMany (mappedBy = "userId") @Fetch (FetchMode.SUBSELECT) privatni popis docs = new ArrayList (); }
Dalje, trebamo servisni sloj s dvije metode za ilustraciju različitih opcija. Jedan od njih je označen kao @Transational. Ovdje obje metode izvode istu logiku brojeći sve dokumente od svih korisnika:
@Service javna klasa ServiceLayer {@Autowired private UserRepository userRepository; @Transactional (readOnly = true) public long countAllDocsTransactional () {return countAllDocs (); } public long countAllDocsNonTransactional () {return countAllDocs (); } private long countAllDocs () {return userRepository.findAll () .stream () .map (User :: getDocs) .mapToLong (Collection :: size) .sum (); }}
Pogledajmo sada bliže sljedeća tri primjera. Također ćemo koristiti SQLStatementCountValidator kako bi se razumjela učinkovitost rješenja, brojeći broj izvršenih upita.
3.2. Lijeno učitavanje s okolnom transakcijom
Prije svega, upotrijebimo lijeno utovar na preporučeni način. Dakle, nazvat ćemo naše @Transational metoda u sloju usluge:
@Test public void whenCallTransactionalMethodWithPropertyOff_thenTestPass () {SQLStatementCountValidator.reset (); long docsCount = serviceLayer.countAllDocsTransactional (); assertEquals (EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount (2); }
Kao što vidimo, ovo djeluje i rezultira u dva povratna putovanja do baze podataka. Prvo putovanje odabire korisnike, a drugo odabire njihove dokumente.
3.3. Lijeno učitavanje izvan transakcije
Nazovimo sada ne-transakcijsku metodu da simuliramo grešku koju dobijemo bez okolne transakcije:
@Test (očekuje se = LazyInitializationException.class) javna praznina kadaCallNonTransactionalMethodWithPropertyOff_thenThrowException () {serviceLayer.countAllDocsNonTransactional (); }
Kao što je i predviđeno, ovo rezultira pogreškom kao getDocs funkcija Korisnik koristi se izvan transakcije.
3.4. Lijeno učitavanje uz automatsku transakciju
Da bismo to popravili, možemo omogućiti svojstvo:
spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true
S uključenom imovinom više ne dobivamo LazyInitializationException.
Međutim, broj upita pokazuje to do baze podataka izvršeno je šest povratnih putovanja. Ovdje jedno povratno putovanje odabire korisnike, a pet povratnih putovanja odabire dokumente za svakog od pet korisnika:
@Test public void whenCallNonTransactionalMethodWithPropertyOn_thenGetNplusOne () {SQLStatementCountValidator.reset (); long docsCount = serviceLayer.countAllDocsNonTransactional (); assertEquals (EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount (EXPECTED_USERS_COUNT + 1); }
Naišli smo na ozloglašeno izdanje N + 1, unatoč činjenici da smo postavili strategiju dohvaćanja kako bismo je izbjegli!
4. Usporedba pristupa
Razgovarajmo ukratko o prednostima i nedostacima.
Kad je imovina uključena, ne moramo se brinuti o transakcijama i njihovim granicama. Hibernate nam to uspijeva.
Međutim, rješenje djeluje polako, jer Hibernate započinje transakciju za nas pri svakom dohvaćanju.
Savršeno funkcionira za demonstracije i kada nas nije briga za probleme s izvedbom. To može biti u redu ako se koristi za dohvaćanje kolekcije koja sadrži samo jedan element ili jedan srodni objekt u odnosu jedan prema jedan.
Bez imovine imamo preciznu kontrolu transakcija, i više se ne suočavamo s problemima izvedbe.
Sve u svemu, ovo nije značajka spremna za proizvodnju, a hibernacijska dokumentacija nas upozorava:
Iako omogućavanje ove konfiguracije može učiniti LazyInitializationException odlazite, bolje je koristiti plan dohvata koji jamči da su sva svojstva pravilno inicijalizirana prije zatvaranja sesije.
5. Zaključak
U ovom uputstvu istražili smo kako se baviti lijenim opterećenjem.
Isprobali smo hibernacijsko svojstvo kako bismo prevladali LazyInitializationException. Također smo vidjeli kako smanjuje učinkovitost i može biti održivo rješenje samo za ograničeni broj slučajeva korištenja.
Kao i uvijek, svi primjeri koda dostupni su na GitHub-u.