uw 2 beste opties om Hibernate ‘ s MultipleBagFetchException

op te lossen U hebt waarschijnlijk geleerd dat u FetchType moet gebruiken.Lui voor al je verenigingen. Het zorgt ervoor dat Hibernate een associatie initialiseert wanneer u het gebruikt en geen tijd besteedt aan het verkrijgen van gegevens die u niet nodig hebt.

helaas introduceert dit een nieuw probleem. Je moet nu een JOIN FETCH-clausule of een EntityGraph gebruiken om de associatie op te halen als je die nodig hebt. Anders ervaart u het n+1 select-probleem, dat ernstige prestatieproblemen of een LazyInitializationException veroorzaakt. Als je dat doet voor meerdere verenigingen, Hibernate zou kunnen gooien een MultipleBagFetchException.

In dit artikel zal ik uitleggen wanneer Hibernate deze uitzondering gooit en u uw 2 beste opties tonen om het op te lossen. Een van hen is een geweldige pasvorm voor verenigingen met een kleine kardinaliteit en de andere voor verenigingen die veel elementen bevatten. Dus, laten we een kijkje nemen op beide, en je kiest degene die past bij uw toepassing.

oorzaak van de MultipleBagFetchException

zoals ik uitlegde in een vorig artikel over het meest efficiënte gegevenstype voor een associatie met veel, is Hibernate ‘ s interne naamgeving van de collectietypen behoorlijk verwarrend. Hibernate noemt het een zak, als de elementen in je java.util.De lijst is niet geordend. Als ze besteld worden, wordt het een lijst genoemd.

dus, afhankelijk van uw toewijzing, een java.util.Lijst kan worden behandeld als een tas of een lijst. Maar maak je geen zorgen, in het echte leven is dit niet zo verwarrend als het lijkt. Het definiëren van de volgorde van een associatie vereist een extra annotatie en is bijna altijd een overhead. Dat is waarom je het moet vermijden en waarom ten minste 90% van de associatie mappings die een java gebruiken.util.Lijst en die ik heb gezien in echte projecten zijn ongeordend. Dus, overwinteren behandelt ze als een zak.

Hier is een eenvoudig domeinmodel waarin Hibernate de Reviews en de auteurs van een boek als zakken behandelt.

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

Als u meerdere van deze zakken probeert op te halen in een jpql-query, maakt u een Cartesiaans product aan.

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

Dit kan prestatieproblemen veroorzaken. Hibernate heeft ook moeite om onderscheid te maken tussen informatie die zou moeten worden gedupliceerd en informatie die werd gedupliceerd vanwege het Cartesiaanse product. Daarom, overwinteren gooit een MultipleBagFetchException.

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

Fixing the MultipleBagFetchException

u kunt veel vragen over deze uitzondering en verschillende oplossingen vinden om dit te voorkomen. Maar veel van hen komen met onverwachte bijwerkingen. De enige 2 fixes waartussen je moet kiezen zijn degenen die Ik zal beschrijven in de volgende secties. Welke het beste voor u is, hangt af van de grootte van het Cartesiaanse product dat uw query ‘ s kunnen aanmaken:

  1. als al uw associaties slechts een klein aantal elementen bevatten, zal het aangemaakte Cartesiaanse product relatief klein zijn. In deze situaties kunt u de typen attributen wijzigen die uw associaties toewijzen aan een java.util.Set. Hibernate kan vervolgens meerdere associaties ophalen in 1 query.
  2. als ten minste één van uw associaties veel elementen bevat, wordt uw Cartesiaans product te groot om het efficiënt op te halen in 1 query. U moet dan gebruik maken van meerdere query ‘ s die verschillende delen van het gewenste resultaat te krijgen.

zoals altijd, vereist het optimaliseren van de prestaties van uw toepassing dat u kiest tussen verschillende trade-offs, en er is geen one-size-fits-all benadering. De prestaties van elke optie zijn afhankelijk van de grootte van het Cartesiaanse product en het aantal queries dat u uitvoert. Voor een relatief klein Cartesiaans product, het krijgen van alle informatie met 1 query biedt u de beste prestaties. Als het Cartesiaanse product een bepaalde grootte bereikt, moet u het beter splitsen in meerdere query ‘ s.

daarom zal ik u beide opties laten zien, zodat u degene kunt kiezen die bij uw toepassing past.

Optie 1: Gebruik een Set in plaats van een lijst

de eenvoudigste aanpak om de MultipleBagFetchException op te lossen is om het type attributen te wijzigen die uw to-many associaties toewijzen aan een java.util.Set. Dit is slechts een kleine verandering in uw mapping, en je hoeft niet om uw business code te wijzigen.

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

zoals eerder uitgelegd, als u nu dezelfde query uitvoert als ik u eerder liet zien om het boek met alle auteurs en beoordelingen te krijgen, zal uw resultaatset een Cartesiaans product bevatten. De grootte van dat product is afhankelijk van het aantal boeken dat u selecteert en het aantal geassocieerde auteurs en Reviews.

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

Hier kunt u de gegenereerde SQL query zien. Om alle gevraagde associaties te krijgen, moet Hibernate alle kolommen selecteren die door deze entiteiten zijn toegewezen. In combinatie met het Cartesiaanse product gemaakt door de 3 INNER JOINs, kan dit een prestatieprobleem worden.

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

wanneer u een dergelijke query schrijft, moet u er ook rekening mee houden dat Hibernate niet verbergt dat de resultaatset een product bevat. Deze query geeft elk boek meerdere keren terug. Het aantal verwijzingen naar hetzelfde Boekobject wordt berekend door het aantal auteurs vermenigvuldigd met het aantal recensies. U kunt dat voorkomen door het toevoegen van de verschillende trefwoord aan uw select clausule en door het instellen van de query hint hibernate.query.passdisthrough naar false.

Prestatieoverwegingen

in dit voorbeeld selecteert mijn zoekopdracht slechts 1 Boek, en de meeste boeken zijn geschreven door 1-3 auteurs. Dus, zelfs als de database meerdere recensies bevat voor dit boek, zal het Cartesiaanse product nog steeds relatief klein zijn.

Op basis van deze aannames kan het sneller zijn om de inefficiëntie van het Cartesiaanse product te accepteren om het aantal queries te verminderen. Dit kan veranderen als uw Cartesiaans product groter wordt omdat u een groot aantal boeken selecteert of als uw gemiddelde boek is geschreven door een paar dozijn auteurs.

Optie 2: splitsen in meerdere query ‘ s

het ophalen van grote Cartesische producten in 1 query is inefficiënt. Het vereist veel resources in uw database en zet onnodige belasting op uw netwerk. Hibernate en je JDBC-stuurprogramma moet ook meer middelen besteden om het query-resultaat af te handelen.

u kunt dat voorkomen door meerdere query ‘ s uit te voeren die verschillende delen van de vereiste grafiek van entiteiten ophalen. In het voorbeeld van deze post, zou ik de boeken met al hun auteurs in Halen 1 query en de boeken met al hun beoordelingen in een 2e query. Als uw grafiek van vereiste entiteiten complexer is, moet u mogelijk meer query ‘ s gebruiken of meer associaties ophalen met elk van hen.

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

zoals ik in het bericht van vorige week heb uitgelegd, zorgt Hibernate ervoor dat er binnen elke sessie slechts 1 entiteitsobject is dat een specifiek record in de database vertegenwoordigt. U kunt dat gebruiken om buitenlandse sleutelverwijzingen efficiënt op te lossen of om de resultaten van meerdere queries in slaapstand samen te voegen.

Als u de volgende log uitvoer bekijkt, kunt u zien dat de lijsten die door beide queries worden geretourneerd precies hetzelfde object bevatten. In beide gevallen hebben de Boekobjecten de referentie @1f.

toen Hibernate het resultaat van de tweede query verwerkte, werd voor elke record gecontroleerd of de cache op het 1e niveau al een object voor die Boekentiteit bevatte. Het vervolgens hergebruikt dat object en voegde de geretourneerde beoordeling aan de toegewezen associatie.

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

Prestatieoverwegingen

als u meerdere query ‘ s gebruikt om de vereiste grafiek van entiteiten te krijgen, vermijdt u het aanmaken van een groot Cartesiaans product. Dit vermindert de belasting van alle betrokken systemen en maakt het gemakkelijker om te zorgen voor een goede prestaties voor alle query ‘ s.

maar dat betekent niet noodzakelijkerwijs dat deze aanpak sneller is dan Optie 1. U voert nu meer zoekopdrachten uit dan voorheen. Elk van hen vereist een database roundtrip en creëert wat beheer overhead in de database, bijvoorbeeld om een uitvoeringsplan te maken. Als gevolg van dat, deze optie is alleen sneller dan Optie 1, als de grootte van de cartesiaanse product creëert een grotere overhead dan de uitvoering van meerdere queries.

conclusie

zoals u in dit artikel hebt gezien, kunt u de MultipleBagFetchException van Hibernate op 2 manieren oplossen:

  • u kunt het gegevenstype van het attribuut dat de associaties toewijst wijzigen en alle informatie in 1 query ophalen. Het resultaat van die vraag is een Cartesiaans product. Zolang dit product niet te groot wordt, is deze aanpak eenvoudig en efficiënt.
  • u kunt meerdere queries gebruiken om de vereiste grafiek van entiteiten op te halen. Dit vermijdt een enorme Cartesiaanse product en is de betere aanpak als je nodig hebt om een enorme hoeveelheid gegevens te halen.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.