Dávkové vkladanie / aktualizácia pomocou režimu dlhodobého spánku / JPA

1. Prehľad

V tomto tutoriáli sa pozrieme na to, ako môžeme dávkové vkladanie alebo aktualizáciu entít pomocou Hibernate / JPA.

Dávkovanie nám umožňuje poslať skupinu príkazov SQL do databázy v jednom sieťovom volaní. Týmto spôsobom môžeme optimalizovať využitie siete a pamäte našej aplikácie.

2. Inštalácia

2.1. Vzorový dátový model

Pozrime sa na náš vzorový dátový model, ktorý použijeme v príkladoch.

Najskôr vytvoríme a Škola subjekt:

@Entity public class School {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) súkromné ​​dlhé ID; súkromné ​​meno reťazca; @OneToMany (mappedBy = "škola") súkromný zoznam študentov; // Zakladatelia a zakladatelia ...}

Každý Škola bude mať nulu alebo viac Študents:

@Entity public class Student {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) súkromné ​​dlhé ID; súkromné ​​meno reťazca; @ManyToOne súkromná školská škola; // Zakladatelia a zakladatelia ...}

2.2. Sledovanie SQL dotazov

Pri spustení našich príkladov budeme musieť overiť, či sa príkazy vloženia / aktualizácie skutočne odosielajú v dávkach. Z výpisov režimu dlhodobého spánku bohužiaľ nemôžeme pochopiť, či sú príkazy SQL dávkové alebo nie. Z tohto dôvodu budeme na sledovanie príkazov Hibernate / JPA SQL používať server proxy zdroja údajov:

súkromná statická trieda ProxyDataSourceInterceptor implementuje MethodInterceptor {súkromný konečný DataSource dataSource; public ProxyDataSourceInterceptor (final DataSource dataSource) {this.dataSource = ProxyDataSourceBuilder.create (dataSource) .name ("Batch-Insert-Logger") .asJson (). countQuery (). logQueryToSysOut (). build (); } // Ostatné metódy ...}

3. Predvolené správanie

Hibernácia predvolene neumožňuje dávkovanie. To znamená, že pre každú operáciu vloženia / aktualizácie pošle samostatný príkaz SQL:

@Transactional @Test public void whenNotConfigured_ThenSendsInsertsSeparately () {for (int i = 0; i <10; i ++) {School school = createSchool (i); entityManager.persist (škola); } entityManager.flush (); }

Tu sme vydržali 10 Škola subjekty. Ak sa pozrieme na protokoly dotazov, môžeme vidieť, že režim dlhodobého spánku odosiela každý príkaz vloženia osobitne:

"querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School1", "1"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School2", "2"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "parametre": [["School3", "3"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School4", "4"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School5", "5"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "parametre": [["School6", "6"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School7", "7"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School8", "8"]] "querySize": 1, "batchSize": 0, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], " params ": [[" School9 "," 9 "]]" querySize ": 1," batchSize ": 0," query ": [" vložiť do školy (meno, id) hodnoty (?,?) "]," params ": [[" School10 "," 10 "]]

Preto by sme mali nakonfigurovať režim dlhodobého spánku tak, aby umožňoval dávkovanie. Pre tento účel, mali by sme nastaviť hibernate.jdbc.batch_size vlastnosť na číslo väčšie ako 0.

Ak tvoríme EntityManager manuálne, mali by sme pridať hibernate.jdbc.batch_size k vlastnostiam dlhodobého spánku:

verejné vlastnosti hibernateProperties () {vlastnosti vlastnosti = nové vlastnosti (); properties.put ("hibernate.jdbc.batch_size", "5"); // Ostatné vlastnosti ... návratové vlastnosti; }

Ak používame Spring Boot, môžeme ho definovať ako vlastnosť aplikácie:

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

4. Dávková vložka pre jeden stôl

4.1. Dávková príloha bez výslovného vypustenia

Poďme sa najskôr pozrieť na to, ako môžeme používať dávkové vložky, keď pracujeme iba s jedným typom entity.

Použijeme predchádzajúcu ukážku kódu, ale tentokrát je dávkovanie povolené:

@Transactional @Test public void whenInsertingSingleTypeOfEntity_thenCreatesSingleBatch () {for (int i = 0; i <10; i ++) {School school = createSchool (i); entityManager.persist (škola); }}

Tu sme vydržali 10 Škola subjekty. Keď sa pozrieme na protokoly, môžeme overiť, či režim dlhodobého spánku odosiela príkazy vloženia v dávkach:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["vložiť do školy (name, id) hodnoty (?,?)"], "params": [["School1" , „1“], [„Škola2“, „2“], [„Škola3“, „3“], [„Škola4“, „4“], [„Škola5“, „5“]] „dávka“: true, "querySize": 1, "batchSize": 5, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School6", "6" ], ["Škola7", "7"], ["Škola8", "8"], ["Škola9", "9"], ["Škola10", "10"]]

Jedna dôležitá vec, ktorú tu treba spomenúť, je spotreba pamäte. Keď perzistujeme entitu, režim dlhodobého spánku ju uloží do kontextu perzistencie. Napríklad, ak v jednej transakcii pretrvávame 100 000 entít, nakoniec máme v pamäti 100 000 inštancií entít, čo by mohlo spôsobiť OutOfMemoryException.

4.2. Dávková príloha s výslovným prepláchnutím

Teraz sa pozrieme na to, ako môžeme optimalizovať využitie pamäte počas dávkových operácií. Poďme hlboko do úlohy kontextu vytrvalosti.

V prvom rade kontext vytrvalosti ukladá do pamäte novovytvorené entity a tiež tie modifikované. Hibernate odošle tieto zmeny do databázy, keď sa transakcia synchronizuje. Spravidla sa to stane na konci transakcie. Avšak volanie EntityManager.flush () tiež spustí synchronizáciu transakcií.

Po druhé, kontext pretrvávania slúži ako medzipamäť entít, a preto sa označuje aj ako medzipamäť prvej úrovne. Na vyčistenie entít v kontexte perzistencie môžeme zavolať EntityManager.clear ().

Takže, aby sme znížili zaťaženie pamäte počas dávkovania, môžeme volať EntityManager.flush () a EntityManager.clear () v našom kóde aplikácie, kedykoľvek sa dosiahne veľkosť dávky:

@Transactional @Test public void whenFlushingAfterBatch_ThenClearsMemory () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } Školská škola = createSchool (i); entityManager.persist (škola); }}

Tu vyprázdňujeme entity v kontexte perzistencie, vďaka čomu program Hibernate odosiela dotazy do databázy. Ďalej vyčistením kontextu perzistencie odstraňujeme Škola entity z pamäti. Dávkové správanie zostane rovnaké.

5. Dávková príloha pre viac tabuliek

Teraz sa pozrime, ako môžeme nakonfigurovať dávkové vloženia pri práci s viacerými typmi entít v jednej transakcii.

Ak chceme zachovať entity niekoľkých typov, režim dlhodobého spánku vytvorí pre každý typ entity inú dávku. To je preto, že v jednej dávke môže byť iba jeden typ entity.

Keď Hibernate zhromažďuje príkazy insert, navyše, kedykoľvek narazí na typ entity, ktorý sa líši od typu entity v aktuálnej dávke, vytvorí novú dávku. Je to tak, aj keď pre daný typ entity už existuje dávka:

@Transactional @Test public void whenThereAreMultipleEntities_ThenCreatesNewBatch () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } Školská škola = createSchool (i); entityManager.persist (škola); Študent firstStudent = createStudent (škola); Študent secondStudent = createStudent (škola); entityManager.persist (firstStudent); entityManager.persist (secondStudent); }}

Sem vkladáme a Škola a priradiť mu dve Študents a tento postup opakovať 10-krát.

V denníkoch vidíme, že Hibernate posiela Škola vložiť príkazy do niekoľkých dávok veľkosti 1, zatiaľ čo sme očakávali iba 2 dávky veľkosti 5. Navyše, Študent príkazy vloženia sa tiež posielajú v niekoľkých dávkach veľkosti 2 namiesto 4 dávok veľkosti 5:

"batch": true, "querySize": 1, "batchSize": 1, "query": ["vložiť do školy (name, id) hodnoty (?,?)"], "params": [["School1" , "1"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["vložiť do hodnoty študenta (meno, identifikátor školy, id) (?,?,?)"] , "params": [["Student-School1", "1", "2"], ["Student-School1", "1", "3"]] "batch": true, "querySize": 1, "batchSize": 1, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School2", "4"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["vložiť do hodnoty študenta (meno, identifikátor školy, id) (?,?,?)"], "parametre": [["Student-School2" , "4", "5"], ["Student-School2", "4", "6"]] "batch": true, "querySize": 1, "batchSize": 1, "query": [" vložte do školy (name, id) hodnoty (?,?) "]," params ": [[" School3 "," 7 "]]" batch ": true," querySize ": 1," batchSize ": 2, "query": ["vložiť do hodnoty študenta (name, school_id, id) (?,?,?)"], "params": [["Student-School3", "7", "8"], [" Student-School3 "," 7 "," 9 "]] Ďalšie riadky denníka ...

Ak chcete dávkovať všetky príkazy vloženia rovnakého typu entity, mali by sme nakonfigurovať hibernate.order_inserts nehnuteľnosť.

Vlastnosť dlhodobého spánku môžeme nakonfigurovať manuálne pomocou EntityManagerFactory:

verejné vlastnosti hibernateProperties () {vlastnosti vlastnosti = nové vlastnosti (); properties.put ("hibernate.order_inserts", "true"); // Ostatné vlastnosti ... návratové vlastnosti; }

Ak používame Spring Boot, môžeme vlastnosť nakonfigurovať v application.properties:

spring.jpa.properties.hibernate.order_inserts = pravda

Po pridaní tejto vlastnosti budeme mať 1 dávku pre Škola vložky a 2 dávky pre Študent vložky:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["vložiť do školy (meno, id) hodnoty (?,?)"], "params": [["School6" , „16“], [„Škola7“, „19“], [„Škola8“, „22“], [„Škola9“, „25“], [„Škola10“, „28“]] „dávka“: true, "querySize": 1, "batchSize": 5, "query": ["vložiť do hodnoty študenta (meno, identifikátor školy, id) (?,?,?)"], "parametre": [["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": [" vložte do hodnoty študenta (name, school_id, id) (?,?,?) "]," params ": [[" "Student-School8", "22", "24"], ["Student-School9", " 25 "," 26 "], [" Student-School9 "," 25 "," 27 "], [" Student-School10 "," 28 "," 29 "], [" Student-School10 "," 28 " , „30“]]

6. Dávková aktualizácia

Prejdime teraz k dávkovým aktualizáciám. Podobne ako v prípade dávkových vložiek môžeme zoskupiť niekoľko aktualizačných príkazov a odoslať ich do databázy naraz.

Ak to chcete povoliť, nakonfigurujeme hibernate.order_updates a hibernate.jdbc.batch_versioned_data vlastnosti.

Ak tvoríme naše EntityManagerFactory manuálne môžeme vlastnosti programovo nastaviť:

verejné vlastnosti hibernateProperties () {vlastnosti vlastnosti = nové vlastnosti (); properties.put ("hibernate.order_updates", "true"); properties.put ("hibernate.batch_versioned_data", "true"); // Ostatné vlastnosti ... návratové vlastnosti; }

A ak používame Spring Boot, pridáme ich len do application.properties:

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

Po nakonfigurovaní týchto vlastností by mal režim dlhodobého spánku zoskupiť príkazy aktualizácie v dávkach:

@Transactional @Test public void whenUpdatingEntities_thenCreatesBatch () {TypedQuery schoolQuery = entityManager.createQuery ("SELECT s from School s", School.class); Zoznam allSchools = schoolQuery.getResultList (); pre (Školská škola: allSchools) {school.setName ("Aktualizované_" + school.getName ()); }}

Tu sme aktualizovali školské entity a program Hibernate odosiela príkazy SQL v 2 dávkach o veľkosti 5:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["aktualizovať školskú súpravu name =? where id =?"], "params": [["Updated_School1", "1" ], ["Aktualizovaná_Škola2", "2"], ["Aktualizovaná_Škola3", "3"], ["Aktualizovaná_Škola4", "4"], ["Aktualizovaná_Škola5", "5"]] "dávka": true, "querySize ": 1," batchSize ": 5," query ": [" update school set name =? Where id =? "]," Params ": [[" Updated_School6 "," 6 "], [" Update_School7 "," 7 "], [" Aktualizovaná_Škola8 "," 8 "], [" Aktualizovaná_Škola9 "," 9 "], [" Aktualizovaná_Škola10 "," 10 "]]

7. @Id Generačná stratégia

Keď chceme použiť dávkovanie pre vloženia / aktualizácie, mali by sme si byť vedomí stratégie generovania primárneho kľúča. Ak naše subjekty používajú GenerationType.IDENTITY generátor identifikátorov, režim dlhodobého spánku ticho zakáže dávkové vkladanie / aktualizácie.

Pretože entity v našich príkladoch používajú GenerationType. SEQUENCE generátor identifikátorov, režim dlhodobého spánku umožňuje dávkové operácie:

@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) súkromné ​​dlhé ID;

8. Zhrnutie

V tomto článku sme sa pozreli na dávkové vloženia a aktualizácie pomocou protokolu Hibernate / JPA.

Pozrite si ukážky kódov pre tento článok na stránkach Github.


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