Java >> Java tutorial >  >> Tag >> native

Sådan bruger du indbyggede forespørgsler til at udføre masseopdateringer

Hvis du blot ønsker at opdatere 1 eller 2 entiteter, kan du blot hente dem fra databasen og udføre opdateringshandlingen på den. Men hvad med at opdatere hundredvis af enheder?

Du kan selvfølgelig bruge standardtilgangen og indlæse og opdatere hver af disse entiteter. Men det er ofte for langsomt, fordi Hibernate udfører en eller flere forespørgsler for at indlæse enheden og en ekstra for at opdatere hver af dem. Dette resulterer hurtigt i et par hundrede SQL-sætninger, som naturligvis er langsommere end blot 1 sætning, som lader databasen gøre arbejdet.

Som jeg forklarer meget detaljeret i Hibernate Performance Tuning Online Training, er antallet af udførte SQL-sætninger afgørende for ydeevnen af ​​din applikation. Så du må hellere have øje med din statistik og holde antallet af udsagn så lavt som muligt. Du kan gøre det med JPQL eller native SQL-forespørgsler, som definerer opdateringen i én sætning.

Det er ret nemt at bruge en native UPDATE-erklæring, som jeg vil vise dig i næste afsnit. Men det skaber også problemer med den altid aktiverede 1. niveau cache og udløser ikke nogen entitets livscyklushændelser. Jeg viser dig, hvordan du håndterer disse problemer i slutningen af ​​indlægget.

Native UPDATE-erklæringer

Du skal bare kalde createNativeQuery metode på EntityManager og giv en indbygget SQL-sætning til den.

em.createNativeQuery("UPDATE person p SET firstname = firstname || '-changed'").executeUpdate();

I dette eksempel opdaterer jeg fornavnet af alle 200 personer i min testdatabase med én forespørgsel. Dette tager omkring 30 ms på min lokale testopsætning.

Den typiske JPA-tilgang ville kræve 200 SELECT erklæringer for at hente hver person-entitet fra databasen og yderligere 200 OPDATERING erklæringer for at opdatere hver af dem. Udførelsen af ​​disse 400 sætninger og al den interne Hibernate-behandling tager omkring 370 ms på min lokale testopsætning.

Jeg har lige brugt System.currentTimeMillis() at måle udførelsestiden på min bærbare computer, som også kører en masse andre applikationer. Opsætningen er langt fra optimal og ikke egnet til en rigtig præstationstest. Så stol ikke på de målte millisekunder. Men det bliver ret indlysende, hvilken tilgang der er den hurtigste, og det er det, det handler om.

Problem 1:Forældet cache på 1. niveau

Hibernate sætter alle entiteter, du bruger i en session, i cachen på første niveau. Dette er ret nyttigt til optimeringer, der kan skrives bagud, og for at undgå duplikerede valg af den samme enhed. Men det skaber også et problem, hvis du bruger en indbygget forespørgsel til at opdatere en masse entiteter.

Hibernate ved ikke, hvilke registrerer de indbyggede forespørgselsopdateringer og kan ikke opdatere eller fjerne de tilsvarende enheder fra cachen på første niveau. Det betyder, at Hibernate bruger en forældet version af entiteten, hvis du hentede den fra databasen, før du udførte den oprindelige SQL UPDATE-sætning. Du kan se et eksempel på det i følgende kodestykke. Begge logerklæringer udskriver det gamle fornavn .

PersonEntity p = em.find(PersonEntity.class, 1L);
	
em.createNativeQuery("UPDATE person p SET firstname = firstname || '-changed'").executeUpdate();
log.info("FirstName: "+p.getFirstName());

p = em.find(PersonEntity.class, 1L);
log.info("FirstName: "+p.getFirstName());

Der er 2 muligheder for at undgå dette problem:

Den mest oplagte er ikke at hente nogen entitet fra databasen, som vil blive påvirket af UPDATE-sætningen. Men vi ved begge, at det ikke er så nemt i en kompleks, modulær applikation.

Hvis du ikke kan undgå at hente nogle af de berørte entiteter, skal du selv opdatere cachen på 1. niveau. Den eneste måde at gøre det på er at adskille dem fra aktiveringspersistenskonteksten og lade Hibernate hente det igen, så snart du har brug for dem. Men vær forsigtig, Hibernate udfører ikke nogen beskidt kontrol, før enheden afmonteres. Så du skal også sørge for, at alle opdateringer er skrevet til databasen, før du frakobler entiteten.
Du kan se et eksempel på det i følgende kodestykke.

PersonEntity p = em.find(PersonEntity.class, 1L);

log.info("Detach PersonEntity");
em.flush();
em.detach(p);

em.createNativeQuery("UPDATE person p SET firstname = firstname || '-changed'").executeUpdate();

p = em.find(PersonEntity.class, 1L);

Som du kan se, kalder jeg flush() og detach() metode på EntityManager før jeg udfører den oprindelige forespørgsel. Kaldet af flush() metode fortæller Hibernate at skrive de ændrede entiteter fra cachen på 1. niveau til databasen. Dette sikrer, at du ikke mister nogen opdatering. Du kan derefter frigøre entiteten fra den aktuelle persistenskontekst og på grund af dette fjerne den fra cachen på 1. niveau.

Problem 2:Ikke en del af enhedens livscyklus

I de fleste applikationer er dette ikke et stort problem. Men jeg vil alligevel nævne det.

Den oprindelige UPDATE-sætning udføres i databasen og bruger ingen entiteter. Dette giver ydeevnefordele, men det undgår også udførelse af enhver enheds livscyklusmetoder eller enhedslyttere.

Hvis du bruger en ramme som Hibernate Envers eller implementerer en hvilken som helst kode selv, der er afhængig af livscyklushændelser, skal du enten undgå native UPDATE-sætninger eller implementere dine lytteres operationer inden for denne specifikke brugssituation.

Oversigt

Med standard JPA-tilgangen henter du en enhed fra databasen og kalder nogle seter-metoder for at opdatere den. Dette føles meget naturligt for Java-udviklere, men antallet af nødvendige SQL-sætninger kan skabe problemer med ydeevnen, hvis du arbejder på et stort sæt entiteter. Det er ofte meget hurtigere at opdatere alle enheder med én indbygget eller JPQL UPDATE-sætning.

Men du skal da passe på din 1. niveaus cache. Hibernate ved ikke, hvilke poster der blev opdateret i databasen og opdaterede ikke de tilsvarende enheder. Du skal enten sikre dig, at du ikke har hentet nogen entiteter, der er berørt af opdateringen, eller du skal afbryde dem fra Dvale-sessionen, før du udfører opdateringen.

Du skal også kontrollere, om du bruger entitets livscyklusmetoder eller enhedslyttere. Den oprindelige UPDATE-sætning bruger ingen entiteter og udløser derfor ikke nogen livscyklushændelse. Hvis du er afhængig af livscyklushændelser, skal du enten undgå native UPDATE-udsagn, eller du skal håndtere de manglende livscyklushændelser i din use case.


Java tag