Stručný sprievodca režimom dlhodobého spánku enable_lazy_load_no_trans

1. Prehľad

Pri používaní lenivého načítania v režime dlhodobého spánku by sme sa mohli stretnúť s výnimkami, keď hovoríme, že neexistuje žiadna relácia.

V tomto tutoriáli si povieme, ako vyriešiť tieto problémy s lenivým načítaním. Aby sme to dosiahli, použijeme Spring Boot na preskúmanie príkladu.

2. Problémy s lenivým načítaním

Cieľom lenivého načítania je ušetriť zdroje načítaním súvisiacich objektov do pamäte pri načítaní hlavného objektu. Namiesto toho odložíme inicializáciu lenivých entít na okamih, kedy sú potrebné. Hibernate používa proxy a zberné obaly na implementáciu lenivého načítania.

Pri načítaní lenivo načítaných údajov prebiehajú dva kroky. Po prvé, je tu vyplnenie hlavného objektu a po druhé, načítanie údajov v rámci jeho proxy serverov. Načítanie údajov vyžaduje vždy otvorené Session v režime dlhodobého spánku.

Problém nastáva, keď dôjde k druhému kroku po uzavretí transakcie, ktorá vedie k a LazyInitializationException.

Odporúčaným prístupom je navrhnúť našu aplikáciu tak, aby sa zabezpečilo, že načítanie údajov sa uskutoční v jednej transakcii. To však môže byť niekedy ťažké, keď sa v inej časti kódu použije lenivá entita, ktorá nedokáže určiť, čo sa načítalo alebo nenačítalo.

Hibernácia má riešenie, enable_lazy_load_no_trans nehnuteľnosť. Zapnutie znamená, že každé načítanie lenivej entity otvorí dočasnú reláciu a spustiť vnútri samostatnej transakcie.

3. Príklad lenivého načítania

Pozrime sa na správanie lenivého načítania v niekoľkých scenároch.

3.1 Nastavenie entít a služieb

Predpokladajme, že máme dve entity, Používateľ a Dokument. Jeden Používateľ môže mať veľa Dokuments, a použijeme @OneToMany opísať ten vzťah. Tiež použijeme @Fetch (FetchMode.SUBSELECT) kvôli efektivite.

Mali by sme poznamenať, že štandardne @OneToMany má lenivý typ načítania.

Poďme si teraz definovať našu Používateľ subjekt:

@Entity verejná trieda Používateľ {// ostatné polia sú pre stručnosť vynechané @OneToMany (mappedBy = "userId") @Fetch (FetchMode.SUBSELECT) private List docs = new ArrayList (); }

Ďalej potrebujeme vrstvu služieb s dvoma metódami na ilustráciu rôznych možností. Jeden z nich je anotovaný ako @ Transakčné. Tu obidve metódy vykonávajú rovnakú logiku spočítaním všetkých dokumentov od všetkých používateľov:

@ Verejná trieda služieb 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 (); }}

Teraz sa pozrime bližšie na nasledujúce tri príklady. Tiež použijeme SQLStatementCountValidator pochopiť efektívnosť riešenia počítaním počtu vykonaných dotazov.

3.2. Lenivé načítanie s okolitou transakciou

Najskôr využime lenivé načítanie odporúčaným spôsobom. Zavoláme teda náš @ Transakčné metóda vo vrstve služby:

@Test public void whenCallTransactionalMethodWithPropertyOff_thenTestPass () {SQLStatementCountValidator.reset (); long docsCount = serviceLayer.countAllDocsTransactional (); assertEquals (EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount (2); }

Ako vidíme, toto funguje a vedie k dva spiatočné lety do databázy. Prvá spiatočná cesta vyberie používateľov a druhá vyberie ich dokumenty.

3.3. Lenivé načítanie mimo transakcie

Teraz nazvime netransakčnú metódu na simuláciu chyby, ktorú dostaneme bez okolitej transakcie:

@Test (očakáva sa = LazyInitializationException.class) public void whenCallNonTransactionalMethodWithPropertyOff_thenThrowException () {serviceLayer.countAllDocsNonTransactional (); }

Ako sa predpokladalo, toto má za následok chybu ako getDocs funkcia Používateľ sa používa mimo transakcie.

3.4. Lenivé načítanie s automatickou transakciou

Aby sme to napravili, môžeme povoliť vlastníctvo:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true

Pri zapnutej nehnuteľnosti už nedostávame a LazyInitializationException.

Počet dotazov to však ukazuje do databázy bolo vykonaných šesť spiatočných letov. Tu jeden obojsmerný výber vyberie používateľov a päť obojsmerných letov vyberie dokumenty pre každého z piatich používateľov:

@Test public void whenCallNonTransactionalMethodWithPropertyOn_thenGetNplusOne () {SQLStatementCountValidator.reset (); long docsCount = serviceLayer.countAllDocsNonTransactional (); assertEquals (EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount (EXPECTED_USERS_COUNT + 1); }

Narazili sme na notoricky známe vydanie N + 1, napriek tomu, že sme nastavili stratégiu načítania, aby sme sa tomu vyhli!

4. Porovnanie prístupov

Poďme si v krátkosti rozobrať klady a zápory.

Keď je táto nehnuteľnosť zapnutá, nemusíme sa starať o transakcie a ich hranice. Hibernácia to za nás spravuje.

Riešenie však funguje pomaly, pretože režim dlhodobého spánku pre nás spustí transakciu pri každom načítaní.

Funguje to perfektne na ukážky a keď sa nestaráme o problémy s výkonom. To môže byť v poriadku, ak sa použije na načítanie kolekcie, ktorá obsahuje iba jeden prvok, alebo jedného súvisiaceho objektu vo vzťahu jedna k jednej.

Bez majetku máme podrobnú kontrolu nad transakciami, a už sa nestretávame s problémami s výkonom.

Celkovo nejde o funkciu pripravenú na výrobua dokumentácia Hibernate nás varuje:

Aj keď umožňuje túto konfiguráciu vykonať LazyInitializationException choďte preč, je lepšie použiť plán načítania, ktorý zaručuje, že všetky vlastnosti sú správne inicializované pred ukončením relácie.

5. Záver

V tomto výučbe sme skúmali riešenie problému lenivého načítania.

Vyskúšali sme vlastnosť dlhodobého spánku, aby sme prekonali LazyInitializationException. Tiež sme videli, ako znižuje účinnosť a môže byť životaschopným riešením iba pre obmedzený počet prípadov použitia.

Všetky príklady kódov sú ako vždy k dispozícii na GitHub.