pravděpodobně Jste se dozvěděl, že byste měli použít FetchType.Líný pro všechny vaše asociace. Zajišťuje, že hibernace inicializuje asociaci, když ji používáte, a netráví čas získáváním dat, která nepotřebujete.
bohužel to přináší nový problém. Nyní musíte použít klauzuli join FETCH nebo EntityGraph k načtení asociace, pokud ji potřebujete. V opačném případě dojde k problému N + 1 select, který způsobuje vážné problémy s výkonem nebo výjimku LazyInitializationException. Pokud tak učiníte pro více asociací, Hibernate může hodit MultipleBagFetchException.
v tomto článku vysvětlím, kdy hibernace hodí tuto výjimku a ukáže vám vaše 2 nejlepší možnosti, jak ji opravit. Jeden z nich je skvělý pro asociace s malou kardinálností a druhý pro asociace, které obsahují spoustu prvků. Podívejme se tedy na oba a vyberete ten, který vyhovuje vaší aplikaci.
Příčinou MultipleBagFetchException
Jak jsem vysvětlil v předchozím článku o nejúčinnější typ dat pro to-many asociace, Hibernace je interní pojmenování typy kolekce je docela matoucí. Hibernace nazývá tašku, pokud prvky v Javě.util.Seznam je neuspořádaný. Pokud jsou objednány, nazývá se to seznam.
takže, v závislosti na mapování, java.util.Seznam lze považovat za tašku nebo seznam. Ale nebojte se, v reálném životě to není tak matoucí, jak by se mohlo zdát. Definování pořadí sdružení vyžaduje další anotaci a je téměř vždy režií. Proto byste se tomu měli vyhnout a proč alespoň 90% asociačních mapování, která používají Javu.util.Seznam a že jsem viděl v reálných projektech jsou neuspořádané. Takže hibernace je zachází jako s taškou.
zde je jednoduchý doménový model, ve kterém Hibernate zachází s recenzemi a autory knihy jako s taškami.
@Entitypublic class Book { @ManyToMany private List authors = new ArrayList(); @OneToMany(mappedBy = "book") private List reviews = new ArrayList(); ... }
Pokud se pokusíte načíst více těchto pytlů v dotazu JPQL, vytvoříte kartézský produkt.
TypedQuery<Book> q = em.createQuery("SELECT DISTINCT b "+ "FROM Book b "+ "JOIN FETCH b.authors a "+ "JOIN FETCH b.reviews r "+ "WHERE b.id = 1",Book.class);q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);List<Book> b = q.getResultList();
to může způsobit problémy s výkonem. Hibernace se také snaží rozlišovat mezi informacemi, které mají být duplikovány, a informacemi, které byly duplikovány kvůli kartézskému produktu. Z tohoto důvodu Hibernate hodí MultipleBagFetchException.
java.lang.IllegalArgumentException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags:
Oprava MultipleBagFetchException
najdete spoustu otázek ohledně této výjimky a různých řešení, jak se jí vyhnout. Ale mnoho z nich přichází s neočekávanými vedlejšími účinky. Jediné opravy 2, mezi kterými byste si měli vybrat, jsou ty, které popíšu v následujících částech. Který z nich je pro vás nejlepší, závisí na velikosti kartézský produkt, který vaše dotazy mohou vytvořit:
- Pokud jsou všechny vaše sdružení obsahují pouze malý počet prvků, je vytvořen kartézský součin bude poměrně malý. V těchto situacích můžete změnit typy atributů, které mapují vaše asociace, na Javu.util.Nastavit. Hibernace pak může načíst více asociací v 1 dotazu.
- pokud alespoň jedna z vašich asociací obsahuje mnoho prvků, váš kartézský produkt bude příliš velký na to, aby jej efektivně načetl v 1 dotazu. Pak byste měli použít více dotazů, které získají různé části požadovaného výsledku.
optimalizace výkonu vaší aplikace jako vždy vyžaduje, abyste si vybrali mezi různými kompromisy a neexistuje žádný univerzální přístup. Výkon každé volby závisí na velikosti kartézského produktu a počtu dotazů, které provádíte. U relativně malého kartézského produktu získáte všechny informace pomocí 1 dotazu nejlepší výkon. Pokud kartézský produkt dosáhne určité velikosti, měli byste jej lépe rozdělit na více dotazů.
proto Vám ukážu obě možnosti, abyste si mohli vybrat tu, která vyhovuje vaší aplikaci.
1. Možnost: Použijte Sadu místo Seznam
nejjednodušší přístup k opravit MultipleBagFetchException je změnit typ atributy, které mapa-mnoho asociací na java.util.Nastavit. Toto je jen malá změna v mapování a nemusíte měnit svůj obchodní kód.
@Entitypublic class Book { @ManyToMany private Set authors = new HashSet(); @OneToMany(mappedBy = "book") private Set reviews = new HashSet(); ... }
Jak bylo vysvětleno dříve, pokud nyní proveďte stejný dotaz jako jsem vám ukazovala předtím, aby si Knihu se všemi jeho Autorů a Recenze, váš výsledek bude obsahovat kartézský součin. Velikost tohoto produktu závisí na počtu knih, které vyberete, a počtu přidružených autorů a recenzí.
TypedQuery<Book> q = em.createQuery("SELECT DISTINCT b "+ "FROM Book b "+ "JOIN FETCH b.authors a "+ "JOIN FETCH b.reviews r "+ "WHERE b.id = 1",Book.class);q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);List<Book> b = q.getResultList();
zde můžete vidět vygenerovaný dotaz SQL. Chcete-li získat všechny požadované asociace, Hibernate musí vybrat všechny sloupce mapované těmito entitami. V kombinaci s kartézským produktem vytvořeným 3 vnitřními spoji se to může stát problémem výkonu.
19:46:20,785 DEBUG - select book0_.id as id1_1_0_, author2_.id as id1_0_1_, reviews3_.id as id1_4_2_, book0_.publisherid as publishe5_1_0_, book0_.publishingDate as publishi2_1_0_, book0_.title as title3_1_0_, book0_.version as version4_1_0_, author2_.firstName as firstNam2_0_1_, author2_.lastName as lastName3_0_1_, author2_.version as version4_0_1_, authors1_.bookId as bookId1_2_0__, authors1_.authorId as authorId2_2_0__, reviews3_.bookid as bookid3_4_2_, reviews3_.comment as comment2_4_2_, reviews3_.bookid as bookid3_4_1__, reviews3_.id as id1_4_1__ from Book book0_ inner join BookAuthor authors1_ on book0_.id=authors1_.bookId inner join Author author2_ on authors1_.authorId=author2_.id inner join Review reviews3_ on book0_.id=reviews3_.bookid where book0_.id=1
kdykoli napíšete takový dotaz, musíte také mít na paměti, že hibernace neskrývá, že sada výsledků obsahuje produkt. Tento dotaz vrací každou knihu vícekrát. Počet odkazů na stejný objekt knihy se vypočítá počtem autorů vynásobeným počtem recenzí. Tomu se můžete vyhnout přidáním odlišného klíčového slova do klauzule select a nastavením hibernace nápovědy dotazu.dotaz.passdistincthrough na false.
úvahy o výkonu
v tomto příkladu můj dotaz vybere pouze 1 knihu a většina knih byla napsána 1-3 autory. Takže i když databáze obsahuje několik recenzí pro tuto knihu, kartézský produkt bude stále relativně malý.
Na základě těchto předpokladů by mohlo být rychlejší přijmout neefektivitu kartézského produktu, aby se snížil počet dotazů. To se může změnit, pokud se váš kartézský produkt zvětší, protože vyberete obrovské množství knih nebo pokud vaše průměrná kniha byla napsána několika desítkami autorů.
možnost 2: rozdělit jej na více dotazů
načítání obrovských kartézských produktů v 1 dotazu je neefektivní. Vyžaduje spoustu zdrojů ve vaší databázi a zbytečně zatěžuje vaši síť. Hibernace a ovladač JDBC také musí vynaložit více prostředků na zpracování výsledku dotazu.
tomu se můžete vyhnout provedením více dotazů, které načtou různé části požadovaného grafu entit. V příkladu tohoto příspěvku bych přinesl knihy se všemi jejich autory v dotazu 1 a knihy se všemi jejich recenzemi v dotazu 2nd. Pokud je váš graf požadovaných entit složitější, možná budete muset použít více dotazů nebo načíst více asociací s každým z nich.
TypedQuery<Book> q = em.createQuery("SELECT DISTINCT b "+ "FROM Book b JOIN FETCH b.authors a "+ "WHERE b.id = 1",Book.class);q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);List<Book> books = q.getResultList();log.info(books.get(0));q = em.createQuery("SELECT DISTINCT b "+ "FROM Book b "+ "JOIN FETCH b.reviews r "+ "WHERE b.id = 1",Book.class);q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);books = q.getResultList();log.info(books.get(0));log.info("Authors: "+books.get(0).getAuthors().size());log.info("Reviews: "+books.get(0).getReviews().size());
Jak jsem vysvětlil v příspěvku z minulého týdne, hibernace zajišťuje, že v každé relaci existuje pouze objekt 1 entity, který představuje konkrétní záznam v databázi. Můžete použít, že k vyřešení cizí klíč odkazuje efektivně nebo nechat Přezimovat sloučit výsledky několika dotazů.
Pokud se podíváte na následující výstup protokolu, uvidíte, že seznamy vrácené oběma dotazy obsahují přesně stejný objekt. V obou případech mají objekty knihy odkaz @1f.
když hibernace zpracovala výsledek 2. dotazu, zkontrolovala pro každý záznam, zda mezipaměť 1. úrovně již obsahovala objekt pro danou účetní entitu. Poté znovu použil tento objekt a přidal vrácenou recenzi do mapované asociace.
19:52:10,600 DEBUG - select book0_.id as id1_1_0_, author2_.id as id1_0_1_, book0_.publisherid as publishe5_1_0_, book0_.publishingDate as publishi2_1_0_, book0_.title as title3_1_0_, book0_.version as version4_1_0_, author2_.firstName as firstNam2_0_1_, author2_.lastName as lastName3_0_1_, author2_.version as version4_0_1_, authors1_.bookId as bookId1_2_0__, authors1_.authorId as authorId2_2_0__ from Book book0_ inner join BookAuthor authors1_ on book0_.id=authors1_.bookId inner join Author author2_ on authors1_.authorId=author2_.id where book0_.id=119:52:10,633 INFO - 19:52:10,645 DEBUG - select book0_.id as id1_1_0_, reviews1_.id as id1_4_1_, book0_.publisherid as publishe5_1_0_, book0_.publishingDate as publishi2_1_0_, book0_.title as title3_1_0_, book0_.version as version4_1_0_, reviews1_.bookid as bookid3_4_1_, reviews1_.comment as comment2_4_1_, reviews1_.bookid as bookid3_4_0__, reviews1_.id as id1_4_0__ from Book book0_ inner join Review reviews1_ on book0_.id=reviews1_.bookid where book0_.id=119:52:10,648 INFO - 19:52:10,648 INFO - Authors: 219:52:10,648 INFO - Reviews: 2
úvahy o výkonu
Pokud použijete více dotazů k získání požadovaného grafu entit, vyhnete se vytvoření obrovského kartézského produktu. To snižuje zatížení všech zapojených systémů a usnadňuje zajištění dobrého výkonu pro všechny dotazy.
ale to nutně neznamená, že tento přístup je rychlejší než volba 1. Nyní provádíte více dotazů než dříve. Každá z nich vyžaduje zpáteční cestu databáze a vytváří v databázi určitou režii správy, například k vytvoření plánu provádění. Díky tomu je tato volba rychlejší než volba 1, pokud velikost kartézského produktu vytváří větší režii než provádění více dotazů.
Závěr
Jak jste viděli v tomto článku, můžete vyřešit Hibernace je MultipleBagFetchException v 2 způsoby:
- můžete změnit typ dat atributu, který mapuje sdružení, a získat všechny informace v 1 dotazu. Výsledkem tohoto dotazu je kartézský produkt. Dokud se tento produkt nedostane příliš velký, je tento přístup jednoduchý a efektivní.
- k načtení požadovaného grafu entit můžete použít více dotazů. Tím se zabrání obrovskému kartézskému produktu a je to lepší přístup, pokud potřebujete načíst obrovské množství dat.