Batchindsats / opdatering med dvale / JPA

1. Oversigt

I denne vejledning ser vi på, hvordan vi kan indsætte eller opdatere enheder ved hjælp af Hibernate / JPA.

Batching giver os mulighed for at sende en gruppe SQL-sætninger til databasen i et enkelt netværksopkald. På denne måde kan vi optimere netværks- og hukommelsesforbruget i vores applikation.

2. Opsætning

2.1. Eksempel på datamodel

Lad os se på vores eksempeldatamodel, som vi bruger i eksemplerne.

For det første opretter vi en Skole enhed:

@Entity public class School {@Id @GeneratedValue (strategi = GenerationType.SEQUENCE) privat lang id; privat strengnavn; @OneToMany (mappedBy = "skole") private studerende på listen; // Getters og setters ...}

Hver Skole har nul eller mere Studerendes:

@Entity public class Student {@Id @GeneratedValue (strategi = GenerationType.SEQUENCE) privat lang id; privat strengnavn; @ManyToOne privatskole skole; // Getters og setters ...}

2.2. Sporing af SQL-forespørgsler

Når vi kører vores eksempler, skal vi kontrollere, at indsæt / opdateringserklæringer faktisk sendes i batches. Desværre kan vi ikke forstå fra dvale-log-sætninger, om SQL-sætninger er batchede eller ej. På grund af dette bruger vi en datakildeproxy til at spore dvaletilstand / JPA SQL-sætninger:

privat statisk klasse ProxyDataSourceInterceptor implementerer MethodInterceptor {privat endelig DataSource dataSource; public ProxyDataSourceInterceptor (final DataSource dataSource) {this.dataSource = ProxyDataSourceBuilder.create (dataSource) .name ("Batch-Insert-Logger") .asJson (). countQuery (). logQueryToSysOut (). build (); } // Andre metoder ...}

3. Standardadfærd

Dvaletilstand aktiverer ikke batching som standard. Dette betyder, at det sender en separat SQL-sætning for hver indsæt / opdateringshandling:

@Transactional @Test ugyldigt nårNotConfigured_ThenSendsInsertsSeparately () {for (int i = 0; i <10; i ++) {School school = createSchool (i); entityManager.persist (skole); } entityManager.flush (); }

Her er vi vedvarende 10 Skole enheder. Hvis vi ser på forespørgselsloggene, kan vi se, at Hibernate sender hver indsætningserklæring separat:

"querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School1", "1"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School2", "2"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School3", "3"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School4", "4"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School5", "5"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School6", "6"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School7", "7"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School8", "8"]] "querySize": 1, "batchSize": 0, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], " params ": [[" School9 "," 9 "]]" querySize ": 1," batchSize ": 0," query ": [" indsæt i skolens (navn, id) værdier (?,?) "]," params ": [[" School10 "," 10 "]]

Derfor skal vi konfigurere dvaletilstand for at aktivere batching. Til dette formål, vi skal sætte hibernate.jdbc.batch_size ejendom til et tal større end 0.

Hvis vi skaber EntityManager manuelt skal vi tilføje hibernate.jdbc.batch_size til dvaleegenskaberne:

offentlige egenskaber hibernateProperties () {Properties egenskaber = nye egenskaber (); egenskaber.put ("hibernate.jdbc.batch_size", "5"); // Andre egenskaber ... returneringsegenskaber; }

Hvis vi bruger Spring Boot, kan vi definere det som en applikationsegenskab:

spring.jpa.properties.hibernate.jdbc.batch_size = 5

4. Batchindsats til enkeltbord

4.1. Batchindsats uden eksplicit skylning

Lad os først se på, hvordan vi kan bruge batchindsatser, når vi kun har at gøre med en enhedstype.

Vi bruger den forrige kodeeksempel, men denne gang er batching aktiveret:

@Transactional @Test offentligt ugyldigt nårInsertingSingleTypeOfEntity_thenCreatesSingleBatch () {for (int i = 0; i <10; i ++) {School school = createSchool (i); entityManager.persist (skole); }}

Her har vi vedvaret 10 Skole enheder. Når vi ser på logfilerne, kan vi kontrollere, at Hibernate sender indsætningserklæringer i batches:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["insert in school (name, id) values ​​(?,?)"], "params": [["School1" , "1"], ["School2", "2"], ["School3", "3"], ["School4", "4"], ["School5", "5"]] "batch": true, "querySize": 1, "batchSize": 5, "query": ["insert in school (name, id) values ​​(?,?)"], "params": [["School6", "6" ], ["School7", "7"], ["School8", "8"], ["School9", "9"], ["School10", "10"]]

En vigtig ting at nævne her er hukommelsesforbruget. Når vi opretholder en enhed, gemmer dvale den i persistens-sammenhængen. For eksempel, hvis vi vedvarer 100.000 enheder i en transaktion, vil vi ende med at have 100.000 enhedsforekomster i hukommelsen og muligvis forårsage en OutOfMemoryException.

4.2. Batchindsats med eksplicit skylning

Nu ser vi på, hvordan vi kan optimere hukommelsesforbruget under batching-operationer. Lad os grave dybt ned i persistens-kontekstens rolle.

Først og fremmest gemmer persistens-konteksten nyoprettede enheder og også de modificerede i hukommelsen. Dvaletilstand sender disse ændringer til databasen, når transaktionen synkroniseres. Dette sker normalt i slutningen af ​​en transaktion. Imidlertid, ringer EntityManager.flush () udløser også en transaktionssynkronisering.

For det andet tjener vedholdenhedskonteksten som en enhedscache, således også kaldet det første niveau cache. For at rydde enheder i vedholdenhedskonteksten kan vi ringe EntityManager.clear ().

Så for at reducere hukommelsesbelastningen under batching kan vi ringe EntityManager.flush () og EntityManager.clear () på vores ansøgningskode, når batchstørrelse nås:

@Transactional @Test offentlig ugyldig nårFlushingAfterBatch_ThenClearsMemory () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } Skoleskole = createSchool (i); entityManager.persist (skole); }}

Her skyller vi enhederne i vedholdenhedskonteksten og får dermed dvale til at sende forespørgsler til databasen. Desuden fjerner vi ved at rydde vedholdenhedskonteksten Skole enheder fra hukommelsen. Batching-opførsel forbliver den samme.

5. Batchindsats til flere tabeller

Lad os nu se, hvordan vi kan konfigurere batchindsatser, når vi beskæftiger os med flere enhedstyper i en transaktion.

Når vi vil fortsætte enhederne af flere typer, opretter dvaletilstand en forskellig batch for hver enhedstype. Dette er fordi der kan kun være en type enhed i en enkelt batch.

Når Hibernate indsamler indsætningserklæringer, opretter den derudover hver gang en enhedstype, der er forskellig fra den i den aktuelle batch, opretter den en ny batch. Dette er tilfældet, selvom der allerede er en batch for den enhedstype:

@Transactional @Test offentlig ugyldig nårThereAreMultipleEntities_ThenCreatesNewBatch () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } Skoleskole = createSchool (i); entityManager.persist (skole); Student firstStudent = createStudent (skole); Student secondStudent = createStudent (skole); entityManager.persist (firstStudent); entityManager.persist (secondStudent); }}

Her indsætter vi en Skole og tildele det to Studerendes og gentage denne proces 10 gange.

I logfilerne ser vi, at dvale sender Skole indsæt udsagn i flere batcher af størrelse 1, mens vi kun forventede 2 batches af størrelse 5. Desuden Studerende indsæt udsagn sendes også i flere batches i størrelse 2 i stedet for 4 batches i størrelse 5:

"batch": true, "querySize": 1, "batchSize": 1, "query": ["insert in school (name, id) values ​​(?,?)"], "params": [["School1" , "1"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["insert in student (name, school_id, id) values ​​(?,?,?)"] , "params": [["Student-School1", "1", "2"], ["Student-School1", "1", "3"]] "batch": true, "querySize": 1, "batchSize": 1, "query": ["indsæt i skolens (navn, id) værdier (?,?)"], "params": [["School2", "4"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["indsæt i elevværdier (navn, school_id, id) (?,?,?)"], "params": [["Student-School2" , "4", "5"], ["Student-School2", "4", "6"]] "batch": true, "querySize": 1, "batchSize": 1, "query": [" indsæt i skole (navn, id) værdier (?,?) "]," params ": [[" School3 "," 7 "]]" batch ": true," querySize ": 1," batchSize ": 2, "forespørgsel": ["indsæt i elevværdier (navn, skole_id, id) (?,?,?)"], "params": [["Student-School3", "7", "8"], [" Student-School3 "," 7 "," 9 "]] Andre loglinjer ...

For at batch alle indsætte udsagn af samme enhedstype, vi skal konfigurere hibernate.order_inserts ejendom.

Vi kan konfigurere egenskaben Dvaletilstand manuelt ved hjælp af EntityManagerFactory:

offentlige egenskaber hibernateProperties () {Properties egenskaber = nye egenskaber (); egenskaber.put ("hibernate.order_inserts", "true"); // Andre egenskaber ... returegenskaber; }

Hvis vi bruger Spring Boot, kan vi konfigurere ejendommen i application.properties:

spring.jpa.properties.hibernate.order_inserts = sandt

Efter tilføjelse af denne egenskab har vi 1 batch til Skole indsatser og 2 batcher til Studerende indsatser:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["insert in school (name, id) values ​​(?,?)"], "params": [["School6" , "16"], ["School7", "19"], ["School8", "22"], ["School9", "25"], ["School10", "28"]] "batch": true, "querySize": 1, "batchSize": 5, "query": ["insert in student (name, school_id, id) values ​​(?,?,?)"], "params": [["Student- School6 "," 16 "," 17 "], [" Student-School6 "," 16 "," 18 "], [" Student-School7 "," 19 "," 20 "], [" Student-School7 " , "19", "21"], ["Student-School8", "22", "23"]] "batch": true, "querySize": 1, "batchSize": 5, "query": [" indsæt i elevværdier (navn, skole_id, id) (?,?,?) "]," params ": [[" Student-School8 "," 22 "," 24 "], [" Student-School9 "," 25 "," 26 "], [" Student-School9 "," 25 "," 27 "], [" Student-School10 "," 28 "," 29 "], [" Student-School10 "," 28 " , "30"]]

6. Batchopdatering

Lad os nu gå videre til batchopdateringer. I lighed med batchindsatser kan vi gruppere flere opdateringserklæringer og sende dem til databasen på én gang.

For at aktivere dette, vi konfigurerer hibernate.order_updates og hibernate.jdbc.batch_versioned_data ejendomme.

Hvis vi skaber vores EntityManagerFactory manuelt kan vi indstille egenskaberne programmatisk:

offentlige egenskaber hibernateProperties () {Properties egenskaber = nye egenskaber (); egenskaber.put ("hibernate.order_updates", "true"); egenskaber.put ("hibernate.batch_versioned_data", "true"); // Andre egenskaber ... returegenskaber; }

Og hvis vi bruger Spring Boot, tilføjer vi dem bare til application.properties:

spring.jpa.properties.hibernate.order_updates = true spring.jpa.properties.hibernate.batch_versioned_data = true

Efter konfiguration af disse egenskaber skal dvale gruppere opdateringserklæringer i batches:

@Transactional @Test offentlig ugyldig nårUpdatingEntities_thenCreatesBatch () {TypedQuery schoolQuery = entityManager.createQuery ("SELECT s from School s", School.class); Liste allSchools = schoolQuery.getResultList (); til (Skoleskole: allSchools) {school.setName ("Opdateret_" + school.getName ()); }}

Her har vi opdateret skoleenheder, og Hibernate sender SQL-sætninger i 2 batches i størrelse 5:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["update school set name =? where id =?"], "params": [["Updated_School1", "1" ], ["Updated_School2", "2"], ["Updated_School3", "3"], ["Updated_School4", "4"], ["Updated_School5", "5"]] "batch": sandt, "querySize ": 1," batchSize ": 5," forespørgsel ": [" opdater skolens sætnavn =? Hvor id =? "]," Params ": [[" Updated_School6 "," 6 "], [" Updated_School7 "," 7 "], [" Updated_School8 "," 8 "], [" Updated_School9 "," 9 "], [" Updated_School10 "," 10 "]]

7. @Id Generationsstrategi

Når vi vil bruge batching til indsatser / opdateringer, skal vi være opmærksomme på den primære nøglegenereringsstrategi. Hvis vores enheder bruger GenerationType.IDENTITY identifikatorgenerator, dvale deaktiverer batchindsatser / opdateringer lydløst.

Da enheder i vores eksempler bruger GenerationType.SEQUENCE identifikatorgenerator, Dvaletilstand muliggør batchoperationer:

@Id @GeneratedValue (strategi = GenerationType.SEQUENCE) privat lang id;

8. Resume

I denne artikel kiggede vi på batchindsatser og opdateringer ved hjælp af Hibernate / JPA.

Tjek kodeeksemplerne til denne artikel på Github.