Batch Insert / Update with Hibernate / JPA

1. Áttekintés

Ebben az oktatóanyagban megvizsgáljuk, hogyan tudunk kötegelt beilleszteni vagy frissíteni az entitásokat a Hibernate / JPA használatával.

A kötegelés lehetővé teszi számunkra, hogy egyetlen hálózati hívás során SQL-utasítások egy csoportját küldjük az adatbázisba. Így optimalizálhatjuk alkalmazásunk hálózat- és memóriahasználatát.

2. Beállítás

2.1. Minta adatmodell

Nézzük meg a példákban használt mintadat-modellünket.

Először létrehozunk egy Iskola entitás:

@Entity public class School {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) privát hosszú azonosító; privát karakterlánc neve; @OneToMany (mappedBy = "iskola") magánlista diákok; // Getters and setters ...}

Minden egyes Iskola nulla vagy több lesz Diáks:

@Entity public class Student {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) private long id; privát karakterlánc neve; @ManyToOne magániskola; // Getters and setters ...}

2.2. SQL lekérdezések nyomon követése

A példáink futtatásakor ellenőriznünk kell, hogy a beszúrási / frissítési utasításokat valóban kötegenként küldik-e. Sajnos a hibernált napló utasításokból nem tudjuk megérteni, hogy az SQL utasítások kötegeltek-e vagy sem. Emiatt egy adatforrás-proxyt fogunk használni a Hibernate / JPA SQL utasítások nyomon követésére:

private static class ProxyDataSourceInterceptor implementálja a MethodInterceptor {private final DataSource dataSource; public ProxyDataSourceInterceptor (final DataSource dataSource) {this.dataSource = ProxyDataSourceBuilder.create (dataSource) .name ("Batch-Insert-Logger") .asJson (). countQuery (). logQueryToSysOut (). build (); } // Egyéb módszerek ...}

3. Alapértelmezett viselkedés

A hibernálás alapértelmezés szerint nem engedélyezi a kötegelt verziót. Ez azt jelenti, hogy minden SQL-beillesztési / frissítési művelethez külön SQL-utasítást küld:

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

Itt maradtunk 10-en Iskola entitások. Ha megnézzük a lekérdezési naplókat, láthatjuk, hogy a Hibernate minden beszúrási utasítást külön küldi:

"querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School1", "1"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School2", "2"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School3", "3"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School4", "4"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School5", "5"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School6", "6"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School7", "7"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School8", "8"]] "querySize": 1, "batchSize": 0, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], " params ": [[" School9 "," 9 "]]" querySize ": 1," batchSize ": 0," query ": [" beszúrás az iskolába (név, id) értékek (?,?) "]," params ": [[" School10 "," 10 "]]

Ezért konfigurálnunk kell a hibernált állapotot a kötegelés engedélyezéséhez. Erre a célra, nekünk kellene beállítanunk hibernate.jdbc.batch_size tulajdonság 0-nál nagyobb számra.

Ha alkotunk EntityManager manuálisan hozzá kell tennünk hibernate.jdbc.batch_size a hibernált tulajdonságokhoz:

public Properties hibernateProperties () {Properties properties = new Properties (); tulajdonságok.put ("hibernálás.jdbc.batch_size", "5"); // Egyéb tulajdonságok ... return tulajdonságok; }

Ha a Spring Boot programot használjuk, meghatározhatjuk azt alkalmazás tulajdonságként:

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

4. Kötegelt betét az egyetlen asztalhoz

4.1. Tételbetét kifejezett öblítés nélkül

Először nézzük meg, hogyan használhatjuk a kötegelt beszúrásokat, amikor csak egy entitástípussal van dolgunk.

Az előző kódmintát fogjuk használni, de ezúttal a kötegelés engedélyezve van:

@Transactional @Test public void whenInsertingSingleTypeOfEntity_thenCreatesSingleBatch () {for (int i = 0; i <10; i ++) {Iskola = createSchool (i); entityManager.persist (iskola); }}

Itt fennmaradt 10 Iskola entitások. Amikor megnézzük a naplókat, ellenőrizhetjük, hogy a hibernálás szakaszosan küldi-e a beszúrási utasításokat:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School1" , "1"], ["Iskola2", "2"], ["Iskola3", "3"], ["Iskola4", "4"], ["Iskola5", "5"]] "köteg": true, "querySize": 1, "batchSize": 5, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School6", "6" ], ["Iskola7", "7"], ["Iskola8", "8"], ["Iskola9", "9"], ["Iskola10", "10"]]

Egy fontos dolog, amit itt meg kell említeni, a memóriafogyasztás. Amikor továbbra is fennáll egy entitás, a Hibernate tárolja azt a perzisztencia kontextusában. Például, ha 100 000 entitást tartunk fenn egy tranzakcióban, akkor 100 000 entitás-példány lesz a memóriában, ami valószínűleg OutOfMemoryException.

4.2. Tételbetét explicit öblítéssel

Most megvizsgáljuk, hogyan optimalizálhatjuk a memóriahasználatot a kötegelt műveletek során. Mélyítsük el mélyen a kitartó kontextus szerepét.

Először is, a perzisztencia-kontextus az újonnan létrehozott entitásokat és a módosítottakat is tárolja a memóriában. A hibernálás a tranzakció szinkronizálásakor elküldi ezeket a változásokat az adatbázisba. Ez általában egy tranzakció végén történik. Azonban, hívás EntityManager.flush () tranzakció szinkronizálást is kivált.

Másodszor, a perzisztencia kontextus entitás gyorsítótárként szolgál, így első szintű gyorsítótárnak is nevezik. Az entitások törléséhez a perzisztencia összefüggésében felhívhatjuk EntityManager.clear ().

Tehát a memória terhelésének csökkentése érdekében kötegelés közben hívhatunk EntityManager.flush () és EntityManager.clear () alkalmazáskódunkon, amikor a kötegméret eléri:

@Transactional @Test public void whenFlushingAfterBatch_ThenClearsMemory () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entitásManager.clear (); } Iskolai iskola = createSchool (i); entityManager.persist (iskola); }}

Itt az entitásokat a perzisztencia-kontextusban öblítjük, így a hibernált állapotban lekérdezéseket küldünk az adatbázisba. Ezenkívül a perzisztencia kontextusának törlésével eltávolítjuk a Iskola entitások a memóriából. A kötegelt viselkedés változatlan marad.

5. Kötegelt betét több asztalhoz

Most nézzük meg, hogyan konfigurálhatjuk a kötegelt beszúrásokat, amikor több entitástípussal foglalkozunk egy tranzakció során.

Amikor több típusú entitást akarunk megtartani, a hibernálás minden entitástípushoz más és más köteget hoz létre. Ez azért van, mert egyetlen tételben csak egy típusú entitás lehet.

Ezenkívül, amikor a hibernált állapot beszúr beszúrási utasításokat, amikor az aktuális kötegben szereplőtől eltérő entitástípussal találkozik, új köteget hoz létre. Ez akkor is így van, bár az entitástípushoz már van köteg:

@Transactional @Test public void whenThereAreMultipleEntities_ThenCreatesNewBatch () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entitásManager.flush (); entitásManager.clear (); } Iskolai iskola = createSchool (i); entityManager.persist (iskola); Student firstStudent = createStudent (iskola); Diák secondStudent = createStudent (iskola); entitásManager.persist (firstStudent); entitásManager.persist (secondStudent); }}

Itt beillesztünk egy Iskola és kettőt rendel hozzá Diáks ezt a folyamatot 10-szer megismételve.

A naplókban azt látjuk, hogy a Hibernate küld Iskola beszúrjon utasításokat több, 1-es méretű tételbe, miközben csak 2 darab 5-ös tételre számítottunk. Diák az insert utasításokat is több, 2 méretű kötegben küldik el, az 5 méretű 4 köteg helyett:

"batch": true, "querySize": 1, "batchSize": 1, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School1" , "1"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["beszúrás a tanuló (név, iskola_azonosító, id) értékekbe (?,?,?)"] , "params": [["Student-School1", "1", "2"], ["Student-School1", "1", "3"]] "batch": true, "querySize": 1, "batchSize": 1, "lekérdezés": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["Iskola2", "4"]] "köteg": igaz, "querySize": 1, "batchSize": 2, "lekérdezés": ["beszúrás a tanuló (név, iskola_azonosító, azonosító) értékekbe (?,?,?)"], "paraméterek": [["Diák-iskola2" , "4", "5"], ["Student-School2", "4", "6"]] "batch": true, "querySize": 1, "batchSize": 1, "query": [" beszúrás az iskolába (név, azonosító) értékek (?,?) "]," params ": [[" School3 "," 7 "]]" batch ": true," querySize ": 1," batchSize ": 2, "lekérdezés": ["beillesztés a tanuló (név, iskola_azonosító, azonosító) értékekbe (?,?,?)"], "paramek": [["Diákiskola3", "7", "8"], [" Student-School3 "," 7 "," 9 "]] Egyéb naplósorok ...

Az azonos entitástípus összes beszúrási utasításának kötegeléséhez konfigurálnunk kell a hibernálni.rendelés_beilleszt ingatlan.

A Hibernate tulajdonságot manuálisan konfigurálhatjuk a EntityManagerFactory:

public Properties hibernateProperties () {Properties properties = new Properties (); tulajdonságok.put ("hibernálás.rend_beillesztések", "igaz"); // Egyéb tulajdonságok ... return tulajdonságok; }

Ha a Spring Boot programot használjuk, konfigurálhatjuk a tulajdonságot az application.properties alkalmazásban:

spring.jpa.properties.hibernate.order_inserts = true

A tulajdonság hozzáadása után 1 kötegünk lesz Iskola betétek és 2 tétel a Diák betétek:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["beszúrás az iskolába (név, azonosító) értékek (?,?)"], "params": [["School6" , "16"], ["Iskola7", "19"], ["Iskola8", "22"], ["Iskola9", "25"], ["Iskola10", "28"]] "köteg": true, "querySize": 1, "batchSize": 5, "query": ["beillesztés a tanuló (név, iskola_azonosító, azonosító) értékekbe (?,?,?)"], "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": [" illessze be a tanuló (név, iskola_azonosító, azonosító) értékeket (?,?,?) "]," params ": [[" Student-School8 "," 22 "," 24 "], [" Student-School9 "," 25 "," 26 "], [" Student-School9 "," 25 "," 27 "], [" Student-School10 "," 28 "," 29 "], [" Student-School10 "," 28 " , "30"]]

6. Kötegelt frissítés

Most térjünk át a kötegelt frissítésekre. A kötegelt beszúrásokhoz hasonlóan több frissítési utasítást is csoportosíthatunk, és egy mozdulattal elküldhetjük az adatbázisba.

Ennek engedélyezéséhez konfiguráljuk hibernate.order_updates és hibernate.jdbc.batch_versioned_data tulajdonságait.

Ha mi alkotjuk a sajátunkat EntityManagerFactory manuálisan beállíthatjuk a tulajdonságokat programozottan:

public Properties hibernateProperties () {Properties properties = new Properties (); properties.put ("hibernate.order_updates", "true"); properties.put ("hibernate.batch_versioned_data", "true"); // Egyéb tulajdonságok ... return tulajdonságok; }

Ha pedig a Spring Boot programot használjuk, akkor csak hozzáadjuk őket az application.properties fájlhoz:

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

Ezeknek a tulajdonságoknak a konfigurálása után a hibernálásnak csoportosítva kell csoportosítania a frissítési utasításokat:

@Transactional @Test public void whenUpdatingEntities_thenCreatesBatch () {TypedQuery schoolQuery = entityManager.createQuery ("SELECT s from School s", School.class); List allSchools = schoolQuery.getResultList (); a (School school: allSchools) {school.setName ("Frissítve_" + iskola.getName ()); }}

Itt frissítettük az iskolai entitásokat, és a Hibernate SQL utasításokat küld 2 kötegben, 5-ös méretben:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["frissítse az iskolakészlet nevét =? hol id =?"], "params": [["Updated_School1", "1" ], ["Updated_School2", "2"], ["Updated_School3", "3"], ["Updated_School4", "4"], ["Updated_School5", "5"]] "batch": true, "querySize ": 1," batchSize ": 5," lekérdezés ": [" frissítse az iskolakészlet nevét? 7 "], [" Updated_School8 "," 8 "], [" Updated_School9 "," 9 "], [" Updated_School10 "," 10 "]]

7. @Id Generációs stratégia

Amikor kötegelt üzenetet akarunk használni a beszúrásokhoz / frissítésekhez, tisztában kell lennünk az elsődleges kulcsgenerálási stratégiával. Ha entitásaink használják GenerationType.IDENTITY azonosító generátor, a hibernálás némán letiltja a kötegelt beillesztéseket / frissítéseket.

Mivel példáinkban szereplő entitások használják GenerationType.SEQUENCE azonosító generátor, a hibernálás lehetővé teszi a kötegelt műveleteket:

@Id @GeneratedValue (stratégia = GenerationType.SEQUENCE) privát hosszú azonosító;

8. Összefoglalás

Ebben a cikkben a kötegelt beszúrásokat és a frissítéseket a Hibernate / JPA segítségével néztük meg.

Nézze meg a cikk kódmintáit a Githubon.