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.