3 Vanlige problemer med dvalemodus og hvordan du finner dem i loggfilen din

1. Introduksjon

Du har sannsynligvis lest noen av klagene på dårlig dvalemodus, eller kanskje du har slitt med noen av dem selv. Jeg har brukt dvalemodus i mer enn 15 år nå, og jeg har fått mer enn nok av disse problemene.

Gjennom årene har jeg lært at disse problemene kan unngås, og at du kan finne mange av dem i loggfilen din. I dette innlegget vil jeg vise deg hvordan du kan finne og fikse tre av dem.

2. Finn og fikse ytelsesproblemer

2.1. Logg SQL-setninger i produksjon

Det første ytelsesproblemet er ekstremt lett å få øye på og ofte ignorert. Det er loggføring av SQL-setninger i et produksjonsmiljø.

Å skrive noen logguttalelser høres ikke ut som noe stort, og det er mange applikasjoner der ute som gjør akkurat det. Men det er ekstremt ineffektivt, spesielt via System.out.println som dvalemodus gjør det hvis du stiller inn vis_sql parameteren i dvalemodus-konfigurasjonen til ekte:

Dvalemodus: velg order0_.id som id1_2_, order0_.orderNumber som orderNum2_2_, order0_.version som versjon3_2_ fra kjøp Bestill ordre0_ Dvalemodus: velg items0_.order_id som order_id4_0_0_, items0_.id som id1_0_0_, items0_id, id_0, id_0, id_0 items0_.product_id som product_5_0_1_, items0_.quantity as kvantitet2_0_1_, items0_.version som versjon3_0_1_ fra OrderItem items0_ hvor items0_.order_id =? Dvalemodus: velg varer0_.order_id som order_id4_0_0_, items0_.id som id1_0_0_, items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.quantity as amount2_0_1_0._version. order_id =? Dvalemodus: velg varer0_.order_id som order_id4_0_0_, items0_.id som id1_0_0_, items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.quantity as amount2_0_1. order_id =?

I et av prosjektene mine forbedret jeg ytelsen med 20% i løpet av få minutter ved å stille inn vis_sql til falsk. Det er den typen prestasjoner du liker å rapportere i neste stand-up møte 🙂

Det er ganske åpenbart hvordan du kan løse dette ytelsesproblemet. Bare åpne konfigurasjonen (f.eks. Persistence.xml-filen) og sett inn vis_sql parameter til falsk. Du trenger ikke denne informasjonen i produksjon uansett.

Men du kan trenge dem under utviklingen. Hvis du ikke gjør det, bruker du to forskjellige dvalemodus-konfigurasjoner (som du ikke burde), du deaktiverte også loggføringen av SQL-setningen der. Løsningen for det er å bruke to forskjellige loggkonfigurasjoner for utvikling og produksjon som er optimalisert for de spesifikke kravene til kjøretidsmiljøet.

Utviklingskonfigurasjon

Utviklingskonfigurasjonen skal gi så mye nyttig informasjon som mulig, slik at du kan se hvordan dvalemodus samhandler med databasen. Du bør derfor i det minste logge de genererte SQL-setningene i utviklingskonfigurasjonen. Du kan gjøre dette ved å aktivere DEBUG melding for org.hibernate.SQL kategori. Hvis du også vil se verdiene til bindingsparametrene dine, må du angi loggnivået på org.hibernate.type.descriptor.sql til SPOR:

log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern =% d {HH: mm: ss, SSS}% -5p [% c] -% m% n log4j.rootLogger = info, stdout # grunnleggende loggnivå for alle meldinger log4j.logger.org.hibernate = info # SQL-setninger og parametere log4j.logger.org.hibernate.SQL = feilsøk log4j.logger.org.hibernate.type.descriptor.sql = spor

Følgende kodebit viser noen eksempler på loggmeldinger som Hibernate skriver med denne loggkonfigurasjonen. Som du kan se, får du detaljert informasjon om den utførte SQL-spørringen og alle angitte og hentede parameterverdier:

23: 03: 22,246 DEBUG SQL: 92 - velg order0_.id som id1_2_, order0_.orderNumber som orderNum2_2_, order0_.version som versjon3_2_ fra kjøp Bestill order0_ hvor order0_.id = 1 23: 03: 22,254 TRACE BasicExtractor: 61 - utvunnet verdi ( [id1_2_]: [BIGINT]) - [1] 23: 03: 22,261 TRACE BasicExtractor: 61 - utvunnet verdi ([orderNum2_2_]: [VARCHAR]) - [order1] 23: 03: 22,263 TRACE BasicExtractor: 61 - utvunnet verdi ( [versjon3_2_]: [INTEGER]) - [0]

Dvalemodus gir deg mye mer intern informasjon om en Økt hvis du aktiverer dvalemodus-statistikken. Du kan gjøre dette ved å sette systemegenskapen dvalemodus.generate_statistics til sant.

Men vær så snill, bare aktiver statistikken over utviklings- eller testmiljøet ditt. Å samle all denne informasjonen bremser søknaden din, og du kan opprette ytelsesproblemer selv hvis du aktiverer den i produksjonen.

Du kan se noen eksempler på statistikk i følgende kodebit:

23: 04: 12,123 INFO StatisticalLoggingSessionEventListener: 258 - Session Metrics {23793 nanosekunder brukt på å skaffe 1 JDBC-forbindelse; 0 nanosekunder brukt på å frigjøre 0 JDBC-forbindelser; 394686 nanosekunder brukte på å forberede 4 JDBC-uttalelser; 2528603 nanosekunder brukte på å utføre 4 JDBC-uttalelser; 0 nanosekunder brukt på å utføre 0 JDBC-batcher; 0 nanosekunder brukt på å utføre 0 L2C putter; 0 nanosekunder brukt på å utføre 0 L2C-treff; 0 nanosekunder brukt på å utføre 0 L2C-savner; 9700599 nanosekunder brukte på å utføre 1 spyling (spyler totalt 9 enheter og 3 samlinger); 42921 nanosekunder brukte på å utføre 1 delvis skylling (spyler totalt 0 enheter og 0 samlinger)}

Jeg bruker regelmessig denne statistikken i det daglige arbeidet mitt for å finne ytelsesproblemer før de oppstår i produksjonen, og jeg kunne skrive flere innlegg omtrent det. Så la oss bare fokusere på de viktigste.

Linjene 2 til 5 viser hvor mange JDBC-tilkoblinger og uttalelser som dvalemodus brukte i løpet av denne økten, og hvor mye tid det brukte på det. Du bør alltid se på disse verdiene og sammenligne dem med dine forventninger.

Hvis det er mange flere utsagn enn du forventet, har du mest sannsynlig det vanligste ytelsesproblemet, n + 1-problemet. Du finner den i nesten alle applikasjoner, og det kan skape store ytelsesproblemer i en større database. Jeg forklarer dette problemet nærmere i neste avsnitt.

Linjene 7 til 9 viser hvordan dvalemodus interagerte med cache på 2. nivå. Dette er en av Hibernates tre cacher, og den lagrer enheter på en øktuavhengig måte. Hvis du bruker 2. nivå i applikasjonen din, bør du alltid overvåke denne statistikken for å se om dvalemodus får enhetene derfra.

Produksjonskonfigurasjon

Produksjonskonfigurasjonen bør optimaliseres for ytelse og unngå meldinger som ikke er presserende. Generelt sett betyr det at du bare skal logge feilmeldinger. Hvis du bruker Log4j, kan du oppnå det med følgende konfigurasjon:

Hvis du bruker Log4j, kan du oppnå det med følgende konfigurasjon:

log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern =% d {HH: mm: ss, SSS}% -5p [% c] -% m% n log4j.rootLogger = info, stdout # grunnleggende loggnivå for alle meldinger log4j.logger.org.hibernate = feil

2.2. N + 1 Velg Utgave

Som jeg allerede har forklart, er n + 1-valgproblemet det vanligste ytelsesproblemet. Mange utviklere klandrer OR-Mapping-konseptet for dette problemet, og de tar ikke helt feil. Men du kan lett unngå det hvis du forstår hvordan dvalemodus behandler dovne forhold. Utvikleren har derfor også skylden fordi det er hans ansvar å unngå slike problemer. Så la meg først forklare hvorfor dette problemet eksisterer, og deretter vise deg en enkel måte å forhindre det på. Hvis du allerede er kjent med n + 1 utvalgte problemer, kan du hoppe direkte til løsningen.

Dvalemodus gir en veldig praktisk kartlegging for forhold mellom enheter. Du trenger bare et attributt med typen tilhørende enhet og noen få merknader for å definere det:

@Entity @Table (name = "purchaseOrder") public class Order implementerer Serializable {@OneToMany (mappedBy = "order", fetch = FetchType.LAZY) private Set items = new HashSet (); ...}

Når du nå laster inn en Rekkefølge enhet fra databasen, trenger du bare å ringe til getItems () metode for å få alle varene i denne bestillingen. Dvalemodus skjuler de nødvendige databasespørsmålene for å få det relaterte Bestillingsvare enheter fra databasen.

Da du begynte med dvalemodus, lærte du sannsynligvis at du burde bruke FetchType.LAZY for de fleste forhold, og at det er standard for mange forhold. Dette forteller dvalemodus å bare hente relaterte enheter hvis du bruker attributtet som kartlegger forholdet. Å hente bare dataene du trenger er generelt bra, men det krever også dvalemodus å utføre et ekstra spørsmål for å initialisere hvert forhold. Dette kan resultere i et stort antall spørsmål, hvis du jobber med en liste over enheter, som jeg gjør i følgende kodebit:

Listeordrer = em.createQuery ("SELECT o FROM Order o"). GetResultList (); for (Order order: orders) {log.info ("Order:" + order.getOrderNumber ()); log.info ("Antall artikler:" + order.getItems (). størrelse ()); }

Du forventer sannsynligvis ikke at disse få kodelinjene kan skape hundrevis eller tusenvis av databasespørsmål. Men det gjør det hvis du bruker FetchType.LAZY for forholdet til Bestillingsvare enhet:

22: 47: 30,065 DEBUG SQL: 92 - velg order0_.id som id1_2_, order0_.orderNumber som orderNum2_2_, order0_.version som versjon3_2_ fra kjøp Bestill bestilling0_ 22: 47: 30,136 INFO NamedEntityGraphTest: 58 - Bestilling: ordre1 22: 47: 30,140 DEBUG SQL: 92 - velg artikler0_.order_id som order_id4_0_0_, items0_.id som id1_0_0_, items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.quantity som kvantitet2_0_1_, items0_.order_id =? 22: 47: 30,171 INFO NamedEntityGraphTest: 59 - Antall artikler: 2 22: 47: 30,171 INFO NamedEntityGraphTest: 58 - Bestilling: order2 22: 47: 30,172 DEBUG SQL: 92 - velg items0_.order_id som order_id4_0_0_, items0_.id som id1_ , items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id as product_5_0_1_, items0_.quantity as amount2_0_1_, items0_.version as version3_0_1_ from OrderItem items0_ where items0_.order_id =? 22: 47: 30,174 INFO NamedEntityGraphTest: 59 - Antall artikler: 2 22: 47: 30,174 INFO NamedEntityGraphTest: 58 - Bestilling: order3 22: 47: 30,174 DEBUG SQL: 92 - velg items0_.order_id som order_id4_0_0_, items0_.id som id1_ , items0_.id som id1_0_1_, items0_.order_id as order_id4_0_1_, items0_.product_id as product_5_0_1_, items0_.quantity as amount2_0_1_, items0_.version as version3_0_1_ from OrderItem items0_ where items0_.order_id =? 22: 47: 30,176 INFO NamedEntityGraphTest: 59 - Antall artikler: 2

Dvalemodus utfører ett spørsmål for å få alle Rekkefølge enheter og en ekstra for hver av n Rekkefølge enheter å initialisere orderItem forhold. Så du vet nå hvorfor denne typen problemer kalles n + 1 select issue og hvorfor det kan skape store ytelsesproblemer.

Det som gjør det enda verre, er at du ofte ikke gjenkjenner det i en liten testdatabase, hvis du ikke har sjekket dvalemodusstatistikken din. Kodebiten krever bare noen få dusin spørsmål hvis testdatabasen ikke inneholder mange ordrer. Men det vil være helt annerledes hvis du bruker den produktive databasen din som inneholder flere tusen av dem.

Jeg sa tidligere at du lett kan unngå disse problemene. Og det er sant. Du må bare initialisere orderItem-forholdet når du velger Rekkefølge enheter fra databasen.

Men vær så snill, bare gjør det hvis du bruker forholdet i bedriftskoden din og ikke bruker FetchType.EAGER å alltid hente relaterte enheter. Det erstatter bare n + 1-problemet ditt med et annet ytelsesproblem.

Initialiser et forhold med en @NamedEntityGraph

Det er flere forskjellige alternativer for å initialisere forhold. Jeg foretrekker å bruke en @NamedEntityGraph som er en av favorittfunksjonene mine introdusert i JPA 2.1. Det gir en spørringsuavhengig måte å spesifisere en graf over enheter som dvalemodus skal hente fra databasen. I følgende kodebit kan du se et eksempel på en enkel graf som lar dvalemodus ivrig hente vareattributtet til en enhet:

@Entity @Table (name = "purchase_order") @NamedEntityGraph (name = "graph.Order.items", attributeNodes = @NamedAttributeNode ("items")) public class Order implementerer Serialiserbar {...}

Det er ikke mye du trenger å gjøre for å definere en enhetsgraf med en @NamedEntityGraph kommentar. Du må bare oppgi et unikt navn for grafen og ett @NamedAttributeNode kommentar for hvert attributt Hibernate skal hente ivrig. I dette eksemplet er det bare elementattributtet som kartlegger forholdet mellom en Rekkefølge og flere Bestillingsvare enheter.

Nå kan du bruke enhetsgrafen til å kontrollere hentingens atferd eller et bestemt spørsmål. Du må derfor instansiere en EntityGraph basert på @NamedEntityGraph definisjon og gi den som et hint til EntityManager.find () metode eller spørringen din. Jeg gjør dette i følgende kodebit der jeg velger Rekkefølge enhet med id 1 fra databasen:

EntityGraph-graf = this.em.getEntityGraph ("graph.Order.items"); Karttips = nytt HashMap (); hints.put ("javax.persistence.fetchgraph", graf); returner this.em.find (Bestill.klasse, 1L, tips);

Dvalemodus bruker denne informasjonen til å lage en SQL-setning som får attributtene til Rekkefølge enhet og attributtene til enhetsgrafen fra databasen:

17: 34: 51,310 DEBUG [org.hibernate.loader.plan.build.spi.LoadPlanTreePrinter] (pool-2-thread-1) LoadPlan (entity = blog. Thoughts.on.java.jpa21.entity.graph.model. Bestilling) - Returnerer - EntityReturnImpl (entity = blog.thoughts.on.java.jpa21.entity.graph.model.Order, querySpaceUid =, path = blog.thoughts.on.java.jpa21.entity.graph.model.Order) - CollectionAttributeFetchImpl (collection = blog.thoughts.on.java.jpa21.entity.graph.model.Order.items, querySpaceUid =, path = blog.thoughts.on.java.jpa21.entity.graph.model.Order.items) - (innsamlingselement) CollectionFetchableElementEntityGraph (entity = blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem, querySpaceUid =, path = blog. Thoughts.on.java.jpa21.entity.graph.model.Order. elementer.) - EntityAttributeFetchImpl (entity = blog.thoughts.on.java.jpa21.entity.graph.model.Product, querySpaceUid =, path = blog. Thoughts.on.java.jpa21.entity.graph.model.Order.items ..produkt) - QuerySpaces - EntityQuerySpaceImpl (uid =, entity = blog. Thoughts.on.java.jpa21.entity.graph.model .Order) - SQL-tabellaliaskartlegging - order0_ - alias-suffiks - 0_ - suffikset nøkkelkolonner - {id1_2_0_} - JOIN (JoinDefinedByMetadata (items)): -> - CollectionQuerySpaceImpl (uid =, collection = blog. Thoughts.on.java. jpa21.entity.graph.model.Order.items) - SQL-tabell aliaskartlegging - items1_ - alias suffiks - 1_ - suffiks nøkkelkolonner - {order_id4_2_1_} - enhets-element alias suffiks - 2_ - 2_entity-element suffiks nøkkelkolonner - id1_0_2_ - JOIN (JoinDefinedByMetadata (elements)): -> - EntityQuerySpaceImpl (uid =, entity = blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem) - SQL-tabellaliaskartlegging - items1_ - alias suffiks - 2_ - suffiksed nøkkelkolonner - {id1_0_2_} - JOIN (JoinDefinedByMetadata (product)): -> - EntityQuerySpaceImpl (uid =, entity = blog. Thoughts.on.java.jpa21.entity.graph.model.Product) - Alias-kartlegging av SQL-tabell - product2_ - alias suffiks - 3_ - suffikset nøkkelkolonner - {id1_1_3_} 17: 34: 51,311 DEBUG [org.hibernate.loader.entity.plan.EntityLoader] (pool-2-thread-1) Statisk velg f eller enhetsblogg. Thoughts.on.java.jpa21.entity.graph.model.Bestill [NONE: -1]: velg order0_.id som id1_2_0_, order0_.orderNumber som orderNum2_2_0_, order0_.version som versjon3_2_0_, items1_.order_id som order_id4_2_1_ , items1_.id som id1_0_1_, items1_.id som id1_0_2_, items1_.order_id as order_id4_0_2_, items1_.product_id as product_5_0_2_, items1_.quantity as quantity2_0_2_, items1_.version as version3_0_2_, product2_.id as product2_3, .versjon som versjon3_1_3_ fra innkjøpsordre ordre0_ venstre ytre sammenføyning OrderItem items1_ på order0_.id = items1_.order_id venstre ytre sammenføyning Product product2_ på items1_.product_id = product2_.id hvor order0_.id =?

Å initialisere bare ett forhold er bra nok for et blogginnlegg, men i et ekte prosjekt vil du mest sannsynlig ønske å bygge mer komplekse grafer. Så la oss gjøre det.

Du kan selvfølgelig tilby en rekke @NamedAttributeNode merknader for å hente flere attributter for samme enhet, og du kan bruke @NamedSubGraph for å definere henteadferd for et ekstra nivå av enheter. Jeg bruker det i følgende kodebit for å hente ikke bare alle relaterte Bestillingsvare enheter, men også Produkt enhet for hver Bestillingsvare:

@Entity @Table (name = "purchase_order") @NamedEntityGraph (name = "graph.Order.items", attributeNodes = @NamedAttributeNode (value = "items", subgraph = "items"), subgraphs = @NamedSubgraph (name = " items ", attributeNodes = @NamedAttributeNode (" product "))) offentlig klasse Bestill implementer Serialiserbar {...}

Som du kan se, er definisjonen av en @NamedSubGraph er veldig lik definisjonen av a @NamedEntityGraph. Du kan deretter referere til denne undergrafen i a @NamedAttributeNode kommentar for å definere hentingens atferd for dette spesifikke attributtet.

Kombinasjonen av disse merknadene lar deg definere komplekse enhetsgrafer som du kan bruke til å initialisere alle relasjoner du bruker i ditt tilfelle, og unngå n + 1 utvalgte problemer. Hvis du vil spesifisere enhetsgrafen dynamisk ved kjøretid, kan du gjøre dette også via et Java API.

2.3. Oppdater enheter en etter en

Å oppdatere enheter en etter en føles veldig naturlig hvis du tenker på en objektorientert måte. Du får bare enhetene du vil oppdatere og kaller noen settermetoder for å endre attributtene sine slik som du gjør det med andre objekter.

Denne tilnærmingen fungerer bra hvis du bare endrer noen få enheter.Men det blir veldig ineffektivt når du jobber med en liste over enheter, og er det tredje ytelsesproblemet du enkelt kan se i loggfilen din. Du må bare se etter en rekke SQL UPDATE-setninger som ser helt like ut, som du kan se i følgende loggfil:

22: 58: 05,829 DEBUG SQL: 92 - velg produkt0_.id som id1_1_, produkt0_.navn som navn2_1_, produkt0_.pris som pris3_1_, produkt0_.versjon som versjon4_1_ fra Produktprodukt0_ 22: 58: 05 883 DEBUG SQL: 92 - oppdatering Produktsett navn = ?, pris = ?, versjon =? hvor id =? og versjon =? 22: 58: 05,889 DEBUG SQL: 92 - oppdatering Produktsettnavn = ?, pris = ?, versjon =? hvor id =? og versjon =? 22: 58: 05,891 DEBUG SQL: 92 - oppdatering Produktsettnavn = ?, pris = ?, versjon =? hvor id =? og versjon =? 22: 58: 05,893 DEBUG SQL: 92 - oppdatering Produktsettnavn = ?, pris = ?, versjon =? hvor id =? og versjon =? 22: 58: 05,900 DEBUG SQL: 92 - oppdatering Produktsettnavn = ?, pris = ?, versjon =? hvor id =? og versjon =?

Den relasjonelle representasjonen av databaseregistrene passer mye bedre for disse brukssakene enn den objektorienterte. Med SQL kan du bare skrive en SQL-setning som oppdaterer alle postene du vil endre.

Du kan gjøre det samme med dvalemodus hvis du bruker JPQL, native SQL eller CriteriaUpdate API. Alle 3 veldig like, så la oss bruke JPQL i dette eksemplet.

Du kan definere en JPQL UPDATE-setning på en lignende måte som du kjenner den fra SQL. Du definerer bare hvilken enhet du vil oppdatere, hvordan du endrer verdiene til attributtene og begrenser de berørte enhetene i WHERE-setningen.

Du kan se et eksempel på det i følgende kodebit der jeg øker prisen på alle produkter med 10%:

em.createQuery ("UPDATE Product p SET p.price = p.price * 0.1"). executeUpdate ();

Dvalemodus oppretter en SQL UPDATE-setning basert på JPQL-setningen og sender den til databasen som utfører oppdateringsoperasjonen.

Det er ganske åpenbart at denne tilnærmingen er mye raskere hvis du må oppdatere et stort antall enheter. Men det har også en ulempe. Dvalemodus vet ikke hvilke enheter som påvirkes av oppdateringsoperasjonen og oppdaterer ikke hurtigbufferen på første nivå. Du bør derfor sørge for å ikke lese og oppdatere en enhet med en JPQL-setning i samme dvalemodus, eller du må koble den for å fjerne den fra hurtigbufferen.

3. Oppsummering

Innen dette innlegget har jeg vist deg 3 Dvale ytelsesproblemer som du finner i loggfilene dine.

To av dem var forårsaket av et stort antall SQL-setninger. Dette er en vanlig årsak til ytelsesproblemer, hvis du jobber med dvalemodus. Dvalemodus skjuler databasetilgangen bak API-en, og det gjør det ofte vanskelig å gjette det faktiske antallet SQL-setninger. Du bør derfor alltid sjekke de utførte SQL-setningene når du gjør en endring i utholdenhetsnivået.


$config[zx-auto] not found$config[zx-overlay] not found