you probably learned that you should learned that you should use FetchType.Laiska kaikille seuroillesi. Se varmistaa, että Hibernate alustaa assosiaation, kun käytät sitä, eikä käytä aikaa hankkiakseen tietoja, joita et tarvitse.
valitettavasti tämä tuo uuden asian esille. Sinun on nyt käytettävä JOIN FETCH-lauseketta tai Entiteettigrafia hakeaksesi assosiaation, jos tarvitset sitä. Muuten, koet N+1 Valitse ongelma, joka aiheuttaa vakavia suorituskykyyn liittyviä ongelmia tai LazyInitializationException. Jos teet, että useita yhdistyksiä, Hibernate saattaa heittää MultipleBagFetchException.
selitän tässä artikkelissa, milloin Hibernate heittää tämän poikkeuksen ja näyttää 2 parasta vaihtoehtoa korjata se. Toinen niistä sopii hyvin yhdistyksille, joissa on pieni kardinaalisuus, ja toinen yhdistyksille, joissa on paljon elementtejä. Niin, katsotaanpa katsomaan molemmat, ja valitset joka sopii hakemuksesi.
syy MultipleBagFetchException
kuten selitin edellisessä artikkelissa tehokkaimmasta tietotyypistä monelle yhdistykselle, Hibernaten sisäinen keräystyyppien nimeäminen on melko hämmentävää. Hibernate kutsuu sitä pussiin, jos elementit java.util.Lista on Järjestämätön. Jos niitä tilataan, sitä kutsutaan listaksi.
joten, riippuen kartoituksesta, java.util.Listaa voidaan käsitellä pussina tai listana. Mutta älä huoli, tosielämässä tämä ei ole niin hämmentävää kuin miltä se saattaa tuntua. Yhdistyksen järjestyksen määrittely vaatii lisähuomautuksen ja on lähes aina ylimenevä. Siksi sinun pitäisi välttää sitä ja miksi vähintään 90% yhdistyksen kartoituksia, jotka käyttävät java.util.Lista ja jonka olen nähnyt todellisissa projekteissa ovat järjestämättömiä. Hibernate kohtelee niitä kuin pussia.
tässä on yksinkertainen domain-malli, jossa Hibernate kohtelee arvostelijoita ja kirjan tekijöitä kuin pusseja.
@Entitypublic class Book { @ManyToMany private List authors = new ArrayList(); @OneToMany(mappedBy = "book") private List reviews = new ArrayList(); ... }
Jos yrität hakea useita näistä pusseista JPQL-kyselyssä, luot karteesisen tuotteen.
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();
Tämä voi aiheuttaa suoritusongelmia. Hibernate kamppailee myös erottaakseen toisistaan informaation, jonka oletetaan olevan päällekkäistä, ja informaation, joka oli päällekkäistä karteesisen tuotteen vuoksi. Tämän vuoksi Hibernate heittää MultipleBagFetchException.
java.lang.IllegalArgumentException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags:
Multiplebagfetchexception vahvistaminen
tästä poikkeuksesta löytyy paljon kysymyksiä ja erilaisia ratkaisuja sen välttämiseksi. Mutta monet niistä tulevat odottamattomia sivuvaikutuksia. Vain 2 korjauksia, joiden välillä sinun pitäisi valita ovat niitä, jotka aion kuvata seuraavissa osissa. Mikä niistä on paras sinulle riippuu siitä, minkä kokoisen karteesisen tuotteen kyselysi voivat luoda:
- Jos kaikki assosiaatiosi sisältävät vain pienen määrän alkuaineita, luotu karteesinen tuote on suhteellisen pieni. Näissä tilanteissa, voit muuttaa tyypit attribuutteja, jotka kartoittavat assosiaatiot java.util.Asettaa. Hibernate voi sitten hakea useita assosiaatioita 1 kyselyssä.
- Jos ainakin yksi assosiaatioistasi sisältää paljon elementtejä, kartesiolainen tuotteesi kasvaa liian suureksi hakeakseen sen tehokkaasti 1-kyselyssä. Sinun pitäisi sitten käyttää useita kyselyitä, jotka saavat eri osia vaaditusta tuloksesta.
kuten aina, sovelluksen suorituskyvyn optimointi vaatii valitsemaan eri kompromissien välillä, eikä ole olemassa yhden koon lähestymistapaa. Kunkin vaihtoehdon suorituskyky riippuu karteesisen tuotteen koosta ja suorittamiesi kyselyjen määrästä. Suhteellisen pieni kartesiolainen tuote, saada kaikki tiedot 1 kysely tarjoaa sinulle parhaan suorituskyvyn. Jos karteesinen tuote saavuttaa tietyn koon, sinun on parempi jakaa se useisiin kyselyihin.
siksi näytän sinulle molemmat vaihtoehdot, jotta voit valita sovellukseesi sopivan vaihtoehdon.
vaihtoehto 1: Käytä joukon sijasta luetteloa
helpoin tapa korjata MultipleBagFetchException on muuttaa niiden attribuuttien Tyyppi, jotka kartoittavat to-many-assosiaatiosi java-muotoon.util.Asettaa. Tämä on vain pieni muutos kartoituksessasi, eikä sinun tarvitse muuttaa yritystunnustasi.
@Entitypublic class Book { @ManyToMany private Set authors = new HashSet(); @OneToMany(mappedBy = "book") private Set reviews = new HashSet(); ... }
kuten aiemmin selitettiin, jos nyt teet saman kyselyn kuin aiemmin näytin saadaksesi kirjan kaikkine Tekijöineen ja arvosteluineen, tuloskokonaisuutesi sisältää karteesisen tuotteen. Tuotteen koko riippuu valitsemiesi kirjojen määrästä ja siihen liittyvien tekijöiden ja arvostelujen määrästä.
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();
täältä näet syntyneen SQL-kyselyn. Saadakseen kaikki pyydetyt yhdistykset, Hibernaten on valittava kaikki näiden entiteettien kartoittamat sarakkeet. Yhdessä karteesisen tuotteen luoma 3 INNER liitokset, tämä voi tulla suorituskyky ongelma.
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
aina kun kirjoitat tällaisen kyselyn, sinun on myös pidettävä mielessä, että Hibernate ei salaa, että tulosjoukko sisältää tuotteen. Tämä kysely palauttaa jokaisen kirjan useita kertoja. Viittausten määrä samaan Kirjakohteeseen lasketaan tekijöiden määrällä kerrottuna arvostelujen määrällä. Voit välttää tämän lisäämällä erillisen avainsanan select-lausekkeeseen ja asettamalla kyselyvihjeen hibernate.kysely.passDistinctThrough to false.
Suoritusnäkökohdat
tässä esimerkissä kyselyni valitsee vain 1 kirjan, ja useimmat kirjat on kirjoittanut 1-3 tekijää. Joten, vaikka tietokanta sisältää useita arvioita tästä kirjasta, kartesiolainen tuote on silti suhteellisen pieni.
näiden oletusten perusteella voisi olla nopeampaa hyväksyä karteesisen tuotteen tehottomuus kyselyjen määrän vähentämiseksi. Tämä saattaa muuttua, jos karteesinen tuotteesi kasvaa, koska valitset valtavan määrän kirjoja tai jos keskivertokirjasi on kirjoittanut muutama kymmenen kirjailijaa.
Vaihtoehto 2: jakaa se useisiin kyselyihin
valtavien kartesiolaisten tuotteiden hakeminen 1 kyselyssä on tehotonta. Se vaatii paljon resursseja tietokantaasi ja kuormittaa tarpeettomasti verkkoasi. Hibernate ja JDBC-ohjain tarvitsevat myös enemmän resursseja kyselyn tuloksen käsittelyyn.
voit välttää sen suorittamalla useita kyselyitä, jotka hakevat eri osia vaaditusta entiteettien kuvaajasta. Tässä esimerkissä tämä viesti, haen kirjoja kaikki niiden kirjoittajat 1 kysely ja kirjoja kaikki arvostelut 2. kysely. Jos kaaviosi vaadituista yksiköistä on monimutkaisempi, saatat joutua käyttämään enemmän kyselyjä tai hakemaan enemmän assosiaatioita kunkin kanssa.
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());
kuten viime viikon viestissä selitin, Hibernate varmistaa, että jokaisessa istunnossa on vain 1 entiteettiobjekti, joka edustaa tiettyä tietuetta tietokannassa. Voit käyttää sitä ratkaisemaan ulkomaisten avain viittauksia tehokkaasti tai antaa Hibernate yhdistää tulokset useita kyselyitä.
Jos katsot seuraavaa lokitulostetta, voit nähdä, että molempien hakujen palauttamat luettelot sisältävät täsmälleen saman objektin. Molemmissa tapauksissa kirja-esineillä on viite @1F.
kun Hibernate käsitteli 2.kyselyn tuloksen, se tarkisti jokaisen tietueen, jos 1. tason välimuisti jo sisälsi objektin kyseiselle kirjakokonaisuudelle. Sen jälkeen se käytti kyseistä esinettä uudelleen ja lisäsi palautetun katsauksen kartoitettuun yhdistykseen.
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
Suoritusnäkökohdat
Jos käyttää useita kyselyitä saadakseen vaaditun kokonaisuuksien kuvaajan, vältetään valtavan karteesisen tuotteen luominen. Tämä vähentää kaikkien mukana olevien järjestelmien kuormitusta ja helpottaa hyvän suorituskyvyn varmistamista kaikissa kyselyissä.
, mutta se ei välttämättä tarkoita, että tämä lähestymistapa olisi nopeampi kuin vaihtoehto 1. Teet nyt enemmän kyselyjä kuin ennen. Jokainen niistä vaatii tietokannan edestakaisen matkan ja luo jonkin verran hallintakustannuksia tietokantaan, esim. toteuttamissuunnitelman luomiseksi. Tästä johtuen tämä vaihtoehto on vain nopeampi kuin vaihtoehto 1, jos karteesisen tuotteen koko luo suuremman yleiskulun kuin useiden kyselyiden suorittaminen.
johtopäätös
kuten olet nähnyt tässä artikkelissa, voit ratkaista Hibernaten MultipleBagFetchException kahdella tavalla:
- voit muuttaa attribuutin tietotyyppiä, joka kartoittaa assosiaatioita ja hakea kaikki tiedot 1 kyselyssä. Kyselyn tulos on karteesinen tuote. Niin kauan kuin tämä tuote ei saa liian suuri, tämä lähestymistapa on yksinkertainen ja tehokas.
- voit käyttää useita kyselyitä hakeaksesi vaaditun kokonaisuuksien kuvaajan. Näin vältetään valtava kartesiolainen tuote ja on parempi lähestymistapa, Jos sinun täytyy hakea valtava määrä tietoa.