A 2 legjobb lehetőség a Hibernate MultipleBagFetchException javítására

valószínűleg megtanulta, hogy használja a FetchType-t.Lusta az összes Egyesülete számára. Ez biztosítja, hogy a Hibernate inicializálja a társítást, amikor használja, és nem tölt el időt a nem szükséges adatok megszerzésével.

sajnos ez új problémát jelent. Most egy JOIN FETCH záradékot vagy egy EntityGraph-ot kell használnia a Társítás lekéréséhez, ha szüksége van rá. Ellenkező esetben az n+1 select problémát fogja tapasztalni, amely súlyos teljesítményproblémákat vagy LazyInitializationException-t okoz. Ha ezt több asszociáció esetén megteszi, a Hibernate egy MultipleBagFetchException-t dobhat.

ebben a cikkben elmagyarázom, amikor a Hibernate dobja ezt a kivételt, és megmutatja a 2 legjobb lehetőséget a javításhoz. Az egyik kiválóan illeszkedik a kis kardinalitású társulásokhoz, a másik pedig a sok elemet tartalmazó társulásokhoz. Vessünk egy pillantást mindkettőre, és kiválasztjuk azt, amelyik megfelel az alkalmazásnak.

A MultipleBagFetchException oka

amint azt egy korábbi cikkben kifejtettem a to-many asszociáció leghatékonyabb adattípusáról, a Hibernate belső elnevezése a gyűjteménytípusokról meglehetősen zavaró. Hibernate nevezi egy zsák, ha az elemek a java.util.A lista rendezetlen. Ha megrendelik őket, akkor ezt listának hívják.

tehát, attól függően, hogy a leképezés, a java.util.Lista lehet kezelni, mint egy zsák vagy egy listát. De ne aggódj, a való életben ez nem olyan zavaró, mint amilyennek tűnhet. Az asszociáció sorrendjének meghatározása további annotációt igényel, és szinte mindig többletköltséget jelent. Ezért érdemes elkerülni, és miért legalább 90% – a társítási leképezések, hogy használja a java.util.A lista, amelyet valódi projektekben láttam, rendezetlen. Tehát a Hibernate zsákként kezeli őket.

itt van egy egyszerű domain modell, amelyben Hibernate kezeli a vélemények és a szerzők egy könyvet, mint táskák.

@Entitypublic class Book { @ManyToMany private List authors = new ArrayList(); @OneToMany(mappedBy = "book") private List reviews = new ArrayList(); ... }

Ha egy jpql lekérdezésben több ilyen zsákot próbál letölteni, akkor létrehoz egy derékszögű terméket.

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

Ez teljesítményproblémákat okozhat. A Hibernate azért is küzd, hogy megkülönböztesse azokat az információkat, amelyeket állítólag meg kell duplikálni, és azokat az információkat, amelyeket a derékszögű termék miatt duplikáltak. Emiatt Hibernate dob egy MultipleBagFetchException.

java.lang.IllegalArgumentException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: 

A MultipleBagFetchException rögzítése

rengeteg kérdést találhat erről a kivételről és különféle megoldásokról annak elkerülésére. De sokuk váratlan mellékhatásokkal jár. Az egyetlen 2 javítás, amelyek között választania kell, azok, amelyeket a következő szakaszokban fogok leírni. A lekérdezések által létrehozott derékszögű termék méretétől függ, hogy melyik a legjobb az Ön számára:

  1. ha az összes Társítás csak kis számú elemet tartalmaz, akkor a létrehozott derékszögű termék viszonylag kicsi lesz. Ezekben a helyzetekben megváltoztathatja azoknak az attribútumoknak a típusait, amelyek a társításokat java-ra térképezik fel.util.Kész. Hibernált ezután letölteni több Egyesület 1 lekérdezés.
  2. ha legalább az egyik asszociáció sok elemet tartalmaz, akkor a derékszögű termék túl nagy lesz ahhoz, hogy hatékonyan lehívja 1 lekérdezésben. Ezután több lekérdezést kell használnia, amelyek a kívánt eredmény különböző részeit kapják meg.

mint mindig, az alkalmazás teljesítményének optimalizálásához különböző kompromisszumok közül kell választania, és nincs mindenki számára megfelelő megközelítés. Az egyes beállítások teljesítménye a derékszögű termék méretétől és a végrehajtott lekérdezések számától függ. Viszonylag kicsi derékszögű termék esetén az összes információ megszerzése 1 lekérdezéssel biztosítja a legjobb teljesítményt. Ha a derékszögű termék elér egy bizonyos méretet, akkor jobban fel kell osztania több lekérdezésre.

ezért megmutatom mindkét lehetőséget, hogy kiválaszthassa azt, amelyik megfelel az alkalmazásának.

1. Lehetőség: készlet használata lista helyett

a MultipleBagFetchException javításának legegyszerűbb módja az attribútumok típusának megváltoztatása, amelyek leképezik a to-many asszociációkat egy java-ra.util.Kész. Ez csak egy kis változás a leképezésben, és nem kell megváltoztatnia az üzleti kódot.

@Entitypublic class Book { @ManyToMany private Set authors = new HashSet(); @OneToMany(mappedBy = "book") private Set reviews = new HashSet(); ... }

amint azt korábban kifejtettük, ha most ugyanazt a lekérdezést hajtja végre, mint amit korábban mutattam, hogy megkapja a könyvet az összes szerzőjével és véleményével, az eredményhalmaz egy derékszögű terméket tartalmaz. A termék mérete a kiválasztott könyvek számától, valamint a kapcsolódó szerzők és vélemények számától függ.

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

itt láthatja a generált SQL lekérdezést. Az összes kért Társítás megszerzéséhez a Hibernate-nek ki kell választania az ezen entitások által leképezett összes oszlopot. A 3 Belső Csatlakozás által létrehozott derékszögű termékkel kombinálva ez teljesítményproblémává válhat.

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

amikor ilyen lekérdezést ír, azt is szem előtt kell tartania, hogy a hibernálás nem rejti el, hogy az eredménykészlet terméket tartalmaz. Ez a lekérdezés minden könyvet többször visszaad. Az ugyanarra a Könyvobjektumra történő hivatkozások számát A szerzők száma szorozza meg a vélemények számával. Ezt elkerülheti, ha hozzáadja a különálló kulcsszót a select záradékhoz, és beállítja a lekérdezési tipp hibernálását.lekérdezés.passDistinctThrough hamis.

teljesítmény szempontok

ebben a példában a lekérdezésem csak 1 könyvet választ ki, és a legtöbb könyvet 1-3 szerző írta. Tehát, még akkor is, ha az adatbázis több véleményt tartalmaz erről a könyvről, a derékszögű termék továbbra is viszonylag kicsi lesz.

Ezen feltételezések alapján gyorsabb lehet elfogadni a derékszögű termék hatékonyságát a lekérdezések számának csökkentése érdekében. Ez megváltozhat, ha a derékszögű termék nagyobb lesz, mert hatalmas számú könyvet választ ki, vagy ha az átlagos könyvet néhány tucat szerző írta.

2. lehetőség: ossza fel több lekérdezésre

hatalmas derékszögű termékek lekérése 1 lekérdezésben nem hatékony. Sok erőforrást igényel az adatbázisban, és felesleges terhelést jelent a hálózaton. A hibernálás és a JDBC illesztőprogramnak is több erőforrást kell költenie a lekérdezés eredményének kezelésére.

ezt elkerülheti, ha több lekérdezést hajt végre, amelyek az entitások szükséges grafikonjának különböző részeit lekérik. A bejegyzés példáján a könyveket az összes Szerzőjükkel 1 lekérdezésben, a könyveket pedig az összes véleményükkel egy 2.lekérdezésben tölteném le. Ha a szükséges entitások grafikonja összetettebb, előfordulhat, hogy több lekérdezést kell használnia, vagy több társítást kell lekérnie mindegyikhez.

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

amint azt a múlt heti bejegyzésben kifejtettem, a Hibernate biztosítja, hogy minden munkameneten belül csak 1 entitás objektum legyen, amely egy adott rekordot képvisel az adatbázisban. Ezt használhatja az idegen kulcshivatkozások hatékony feloldásához, vagy a hibernálás több lekérdezés eredményeinek egyesítéséhez.

Ha megnézzük a következő naplókimenetet, láthatjuk, hogy a két lekérdezés által visszaadott listák pontosan ugyanazt az objektumot tartalmazzák. A Könyvobjektumok mindkét esetben @1F hivatkozással rendelkeznek.

amikor a Hibernate feldolgozta a 2.lekérdezés eredményét, minden rekordnál ellenőrizte, hogy az 1. szintű gyorsítótár már tartalmaz-e objektumot az adott könyv entitás számára. Ezután újra felhasználta az objektumot, és hozzáadta a visszaküldött áttekintést a leképezett társításhoz.

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

teljesítmény szempontok

Ha több lekérdezést használ az entitások szükséges grafikonjának megszerzéséhez, akkor elkerülheti egy hatalmas derékszögű termék létrehozását. Ez csökkenti az összes érintett rendszer terhelését, és megkönnyíti az összes lekérdezés jó teljesítményének biztosítását.

de ez nem feltétlenül jelenti azt, hogy ez a megközelítés gyorsabb, mint az 1.Lehetőség. Most több lekérdezést hajt végre, mint korábban. Mindegyikhez szükség van egy adatbázis-Körútra, és létrehoz néhány kezelési költséget az adatbázisban, például egy végrehajtási terv létrehozásához. Emiatt ez az opció csak gyorsabb, mint az 1.opció, ha a derékszögű termék mérete nagyobb rezsi, mint több lekérdezés végrehajtása.

következtetés

amint azt ebben a cikkben láthattuk, a Hibernate MultipleBagFetchException 2 módon oldható meg:

  • megváltoztathatja az attribútum adattípusát, amely leképezi az asszociációkat és lekéri az összes információt az 1 lekérdezésben. A lekérdezés eredménye egy derékszögű termék. Amíg ez a termék nem lesz túl nagy, ez a megközelítés egyszerű és hatékony.
  • több lekérdezést is használhat az entitások szükséges grafikonjának lekéréséhez. Ez elkerüli a hatalmas derékszögű terméket, és a jobb megközelítés, ha hatalmas mennyiségű adatot kell lekérnie.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.