du har sikkert lært, at du skal bruge FetchType.Doven for alle dine foreninger. Det sikrer, at Hibernate initialiserer en tilknytning, når du bruger den og ikke bruger nogen tid på at få data, du ikke har brug for.
desværre introducerer dette et nyt problem. Du skal nu bruge en JOIN FETCH-klausul eller en EntityGraph til at hente foreningen, hvis du har brug for det. Ellers vil du opleve N+1 select-problemet, som forårsager alvorlige ydelsesproblemer eller en doven Initialiseringsundtagelse. Hvis du gør det for flere foreninger, Hibernate kan kaste en Multiplebagfetch undtagelse.
i denne artikel vil jeg forklare, hvornår dvaletilstand kaster denne undtagelse og viser dig dine 2 bedste muligheder for at ordne det. En af dem passer godt til foreninger med en lille kardinalitet og den anden til foreninger, der indeholder mange elementer. Så lad os se på dem begge, og du vælger den der passer til din ansøgning.
årsag til Multiplebagfetchundtagelsen
som jeg forklarede i en tidligere artikel om den mest effektive datatype for en til mange forening, er hibernates interne navngivning af samlingstyperne ret forvirrende. Hibernate kalder det en taske, hvis elementerne i din java.util.Listen er uordnet. Hvis de er bestilt, kaldes det en liste.
så afhængigt af din kortlægning, en java.util.Liste kan behandles som en taske eller en liste. Men rolig, i det virkelige liv er dette ikke så forvirrende, som det kan synes. At definere rækkefølgen af en forening kræver en yderligere annotation og er næsten altid en overhead. Det er derfor, du bør undgå det, og hvorfor mindst 90% af foreningen tilknytninger, der bruger en java.util.Liste, og at jeg har set i virkelige projekter er uordnet. Så Hibernate behandler dem som en taske.
Her er en simpel domænemodel, hvor Hibernate behandler anmeldelser og forfattere af en bog som poser.
@Entitypublic class Book { @ManyToMany private List authors = new ArrayList(); @OneToMany(mappedBy = "book") private List reviews = new ArrayList(); ... }
Hvis du forsøger at hente flere af disse poser i en JPKL-forespørgsel, opretter 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 skabe ydelsesproblemer. Hibernate kæmper også for at skelne mellem information, der formodes at blive duplikeret, og information, der blev duplikeret på grund af det kartesiske produkt. På grund af det, Hibernate kaster en Multiplebagfetch undtagelse.
java.lang.IllegalArgumentException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags:
fastsættelse af Multiplebagfetchundtagelse
Du kan finde mange spørgsmål om denne undtagelse og forskellige løsninger for at undgå det. Men mange af dem kommer med uventede bivirkninger. De eneste 2 rettelser, som du skal vælge mellem, er dem, som jeg vil beskrive i de følgende afsnit. Hvilken af dem der er bedst for dig afhænger af størrelsen på det kartesiske produkt, som dine forespørgsler muligvis opretter:
- hvis alle dine foreninger kun indeholder et lille antal elementer, vil det oprettede kartesiske produkt være relativt lille. I disse situationer kan du ændre typerne af de attributter, der knytter dine tilknytninger til en java.util.Indstille. Hibernate kan derefter hente flere foreninger i 1 forespørgsel.
- hvis mindst en af dine foreninger indeholder mange elementer, bliver dit kartesiske produkt for stort til at hente det effektivt i 1 forespørgsel. Du skal derefter bruge flere forespørgsler, der får forskellige dele af det ønskede resultat.
som altid kræver optimering af ydeevnen for din applikation, at du vælger mellem forskellige afvejninger, og der er ingen tilgang, der passer til alle. Ydeevnen for hver indstilling afhænger af størrelsen på det kartesiske produkt og antallet af forespørgsler, du udfører. For et relativt lille kartesisk produkt, at få alle oplysninger med 1 forespørgsel giver dig den bedste ydelse. Hvis det kartesiske produkt når en bestemt størrelse, bør du bedre opdele det i flere forespørgsler.
derfor vil jeg vise dig begge muligheder, så du kan vælge den der passer til din ansøgning.
mulighed 1: Brug et sæt i stedet for en liste
den nemmeste tilgang til at rette Multiplebagfetchundtagelsen er at ændre typen af de attributter, der kortlægger dine til-mange foreninger til en java.util.Indstille. Dette er kun en lille ændring i din kortlægning, og du behøver ikke at ændre din Virksomhedskode.
@Entitypublic class Book { @ManyToMany private Set authors = new HashSet(); @OneToMany(mappedBy = "book") private Set reviews = new HashSet(); ... }
Som forklaret tidligere, hvis du nu udfører den samme forespørgsel, som jeg viste dig før for at få bogen med alle dens forfattere og anmeldelser, vil dit resultatsæt indeholde et kartesisk produkt. Størrelsen af dette produkt afhænger af antallet af bøger, du vælger, og antallet af 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 genererede forespørgsel. For at få alle de ønskede foreninger skal Hibernate vælge alle kolonner, der er kortlagt af disse enheder. I kombination med det kartesiske produkt, der er oprettet af de 3 indre sammenføjninger, kan dette blive et ydelsesproblem.
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 sådan forespørgsel, skal du også huske, at Hibernate ikke skjuler, at resultatsættet indeholder et produkt. Denne forespørgsel returnerer hver bog flere gange. Antallet af henvisninger til det samme Bogobjekt beregnes med antallet af forfattere ganget med antallet af anmeldelser. Du kan undgå det ved at tilføje det særskilte søgeord til din select-klausul og ved at indstille forespørgselstip dvale.forespørgsel.passDistinctThrough til falsk.
Ydelsesovervejelser
i dette eksempel vælger min forespørgsel kun 1 Bog, og de fleste bøger er skrevet af 1-3 forfattere. Så selvom databasen indeholder flere anmeldelser til denne bog, vil det kartesiske produkt stadig være relativt lille.
baseret på disse antagelser kan det være hurtigere at acceptere ineffektiviteten af det kartesiske produkt for at reducere antallet af forespørgsler. Dette kan ændre sig, hvis dit kartesiske produkt bliver større, fordi du vælger et stort antal bøger, eller hvis din gennemsnitlige bog er skrevet af et par dusin forfattere.
Mulighed 2: opdel det i flere forespørgsler
hentning af enorme kartesiske produkter i 1 forespørgsel er ineffektiv. Det kræver mange ressourcer i din database og lægger unødvendig belastning på dit netværk. Hibernate og din jdbc-driver skal også bruge flere ressourcer til at håndtere forespørgselsresultatet.
Du kan undgå det ved at udføre flere forespørgsler, der henter forskellige dele af den krævede graf over enheder. I eksemplet på dette indlæg ville jeg hente bøgerne med alle deres forfattere i 1 forespørgsel og Bøgerne med alle deres anmeldelser i en 2.forespørgsel. Hvis din graf over krævede enheder er mere kompleks, skal du muligvis bruge flere forespørgsler eller hente flere tilknytninger til hver af 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 forklarede i sidste uges indlæg, sikrer Hibernate, at der inden for hver Session kun er 1 enhedsobjekt, der repræsenterer en bestemt post i databasen. Du kan bruge det til at løse udenlandske nøglereferencer effektivt eller til at lade Hibernate flette resultaterne af flere forespørgsler.
Hvis du kigger på følgende logoutput, kan du se, at listerne, der returneres af begge forespørgsler, indeholder nøjagtigt det samme objekt. I begge tilfælde har Bogobjekterne referencen @1F.
Når dvaletilstand behandlede resultatet af 2. forespørgsel, kontrolleres det for hver post, hvis cachen på 1. niveau allerede indeholdt et objekt for den Bogenhed. Det genbrugte derefter objektet og tilføjede den returnerede anmeldelse til den kortlagte forening.
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
Ydelsesovervejelser
Hvis du bruger flere forespørgsler til at få den krævede graf over enheder, undgår du oprettelsen af et stort kartesisk produkt. Dette reducerer belastningen på alle involverede systemer og gør det lettere at sikre en god ydelse for alle forespørgsler.
men det betyder ikke nødvendigvis, at denne tilgang er hurtigere end Mulighed 1. Du udfører nu flere forespørgsler end før. Hver af dem kræver en database roundtrip og skaber nogle administrationsomkostninger i databasen, f.eks. På grund af det er denne mulighed kun hurtigere end Mulighed 1, hvis størrelsen på det kartesiske produkt skaber en større overhead end udførelsen af flere forespørgsler.
konklusion
som du har set i denne artikel, kan du løse hibernates Multiplebagfetchundtagelse på 2 måder:
- du kan ændre datatypen for attributten, der kortlægger tilknytningerne og hente alle oplysninger i 1 forespørgsel. Resultatet af denne forespørgsel er et kartesisk produkt. Så længe dette produkt ikke bliver for stort, er denne tilgang enkel og effektiv.
- du kan bruge flere forespørgsler til at hente den ønskede graf over enheder. Dette undgår et enormt kartesisk produkt og er den bedre tilgang, hvis du har brug for at hente en enorm mængde data.