Dine 2 beste alternativer for å fikse Hibernate ‘ S MultipleBagFetchException

du har sikkert lært at du bør bruke FetchType.LAT for alle dine foreninger. Det sikrer At Hibernate initialiserer en forening når du bruker den og ikke bruker noe tid på å få data du ikke trenger.

Dessverre introduserer Dette et nytt problem. Du må nå bruke EN JOIN FETCH-klausul eller En EntityGraph for å hente foreningen hvis du trenger det. Ellers vil du oppleve n+1 select-problemet, som forårsaker alvorlige ytelsesproblemer eller En LazyInitializationException. Hvis du gjør det for flere foreninger, Kan Hibernate kaste En MultipleBagFetchException.I denne artikkelen vil jeg forklare Når Hibernate kaster dette unntaket og vise deg dine 2 beste alternativer for å fikse det. En av dem er en god passform for foreninger med en liten kardinalitet og den andre for foreninger som inneholder mange elementer. Så, la oss ta en titt på begge, og du velger den som passer din søknad.

Årsak Til MultipleBagFetchException

Som jeg forklarte i en tidligere artikkel om den mest effektive datatypen for en til-mange forening, Er Hibernates interne navngivning av samlingstypene ganske forvirrende. Hibernate kaller det En Pose, hvis elementene i java.util.Listen er uordnet. Hvis de er bestilt, kalles Det En Liste.

Så, avhengig av kartleggingen din, en java.util.Listen kan behandles Som En Pose eller En Liste. Men ikke bekymre deg, i det virkelige liv, dette er ikke så forvirrende som det kan virke. Definere rekkefølgen av en forening krever en ekstra merknad og er nesten alltid en overhead. Det er derfor du bør unngå det og hvorfor minst 90% av foreningens kartlegginger som bruker en java.util.Liste og som jeg har sett i virkelige prosjekter er uordnet. Så, Hibernate behandler dem som En Pose.

Her er en enkel domenemodell Der Hibernate behandler Anmeldelser og Forfattere Av En Bok Som Poser.

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

hvis du prøver å hente flere av disse posene i EN jpql-spørring, oppretter du et kartesisk 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();

dette kan skape ytelsesproblemer. Hibernate sliter også med å skille mellom informasjon som skal dupliseres og informasjon som ble duplisert på grunn av det kartesiske produktet. På grunn av Det Kaster Hibernate En MultipleBagFetchException.

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

Fikse MultipleBagFetchException

du kan finne mange spørsmål om dette unntaket og ulike løsninger for å unngå det. Men mange av dem kommer med uventede bivirkninger. De eneste 2 reparasjonene mellom hvilke du bør velge er de som jeg vil beskrive i de følgende avsnittene. Hvilken av dem som er best for deg, avhenger av størrelsen på det kartesiske produktet som spørringene dine kan opprette:

  1. Hvis alle foreningene dine bare inneholder et lite antall elementer, vil det opprettede kartesiske produktet være relativt lite. I disse situasjonene kan du endre typene attributter som tilordner tilknytningene dine til en java.util.Angi. Hibernate kan deretter hente flere foreninger i 1 spørring.
  2. hvis minst en av foreningene dine inneholder mange elementer, blir ditt kartesiske produkt for stort til å hente det effektivt i 1 spørring. Du bør da bruke flere spørringer som får forskjellige deler av det nødvendige resultatet.

som alltid, optimalisere ytelsen til programmet krever at du velger mellom ulike avveininger, og det er ingen one-size-fits-all tilnærming. Ytelsen til hvert alternativ avhenger av størrelsen på det kartesiske produktet og antall spørringer du utfører. For et relativt lite kartesisk produkt, får all informasjon med 1 spørring deg den beste ytelsen. Hvis det kartesiske produktet når en viss størrelse, bør du bedre dele den i flere spørringer.

Det er derfor jeg vil vise deg begge alternativene, slik at du kan velge den som passer din søknad.

Alternativ 1: Bruk Et Sett i stedet for En Liste

den enkleste tilnærmingen til å fikse MultipleBagFetchException er å endre typen attributter som tilordner til mange foreninger til en java.util.Angi. Dette er bare en liten endring i kartleggingen, og du trenger ikke å endre bedriftskoden din.

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

som forklart tidligere, hvis du nå utfører samme spørring som jeg viste deg før for å få Boken med Alle Sine Forfattere og Anmeldelser, vil resultatsettet inneholde et kartesisk produkt. Størrelsen på dette produktet avhenger av Antall Bøker du velger og antall tilknyttede Forfattere og Anmeldelser.

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

her kan du se DEN genererte SQL-spørringen. For å få alle de forespurte foreningene, Må Hibernate velge alle kolonner som er kartlagt av disse enhetene. I kombinasjon med det kartesiske produktet skapt av de 3 INDRE Leddene, kan dette bli et ytelsesproblem.

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

når du skriver en slik spørring, må du også huske på At Hibernate ikke skjuler at resultatsettet inneholder et produkt. Denne spørringen returnerer Hver Bok flere Ganger. Antall referanser til Samme Bokobjekt beregnes av Antall Forfattere multiplisert med Antall Vurderinger. Du kan unngå det ved å legge TIL DET DISTINKTE søkeordet i select-klausulen din og ved å sette spørringshintet dvalemodus.spørsmål.passDistinctThrough til false.

ytelseshensyn

i dette eksemplet velger spørringen min bare 1 Bok, og de fleste Bøker er skrevet av 1-3 Forfattere. Så, selv om databasen inneholder flere Vurderinger for Denne Boken, vil det kartesiske produktet fortsatt være relativt lite. basert på disse antagelsene kan det være raskere å akseptere ineffektiviteten til det kartesiske produktet for å redusere antall spørringer. Dette kan endres hvis ditt kartesiske produkt blir større fordi du velger et stort antall Bøker, eller hvis din gjennomsnittlige Bok er skrevet av noen få dusin Forfattere.

Alternativ 2: Del det i flere spørringer

Å Hente store kartesiske produkter i 1 spørring er ineffektiv. Det krever mye ressurser i databasen og setter unødvendig belastning på nettverket. Hibernate og jdbc-driveren må også bruke mer ressurser til å håndtere spørringsresultatet.

du kan unngå det ved å utføre flere spørringer som henter forskjellige deler av den nødvendige grafen for enheter. I eksemplet på dette innlegget ville jeg hente Bøkene med alle Sine Forfattere i 1-spørring og Bøkene med alle Sine Vurderinger i en 2. spørring. Hvis grafen over nødvendige enheter er mer kompleks, må du kanskje bruke flere spørringer eller hente flere tilknytninger til hver av dem.

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

Som jeg forklarte i forrige ukes innlegg, Sikrer Hibernate at I hver Økt er det bare 1 enhet objekt som representerer en bestemt post i databasen. Du kan bruke Det til å løse utenlandske nøkkelreferanser effektivt eller for Å la Dvalemodus slå sammen resultatene av flere spørringer.

hvis du ser på følgende loggutgang, kan Du se At Listene som returneres av begge spørringene, inneholder nøyaktig det samme objektet. I Begge tilfeller Har Bokobjektene referansen @1f.

Når Dvalemodus behandlet resultatet av 2. spørring, sjekket det for hver post hvis 1. nivåbufferen allerede inneholdt et objekt for Den bokenheten. Det deretter gjenbrukt objektet og lagt den returnerte Anmeldelsen til den tilordnede foreningen.

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

ytelseshensyn

hvis du bruker flere spørringer for å få den nødvendige grafen av enheter, unngår du opprettelsen av et stort kartesisk produkt. Dette reduserer belastningen på alle involverte systemer og gjør det enklere å sikre god ytelse for alle spørringer.

men det betyr ikke nødvendigvis at denne tilnærmingen er raskere enn alternativ 1. Du utfører nå flere spørringer enn før. Hver av dem krever en database rundtur og skaper noen ledelse overhead i databasen, f. eks, for å lage en gjennomføringsplan. På grunn av dette er dette alternativet bare raskere enn alternativ 1, hvis størrelsen på det kartesiske produktet skaper en større overhead enn utførelsen av flere spørringer.

Konklusjon

Som du har sett i denne artikkelen, kan Du løse Hibernates MultipleBagFetchException på 2 måter:

  • du kan endre datatypen til attributtet som kartlegger foreningene og hente all informasjon i 1 spørring. Resultatet av denne spørringen er et kartesisk produkt. Så lenge dette produktet ikke blir for stort, er denne tilnærmingen enkel og effektiv.
  • Du kan bruke flere spørringer for å hente den nødvendige grafen av enheter. Dette unngår et stort kartesisk produkt og er bedre tilnærming hvis du trenger å hente en stor mengde data.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.