Batch invoegen / bijwerken met slaapstand / JPA

1. Overzicht

In deze zelfstudie bekijken we hoe we entiteiten batchgewijs kunnen invoegen of bijwerken met behulp van Hibernate / JPA.

Met batching kunnen we een groep SQL-instructies in één netwerkoproep naar de database sturen. Op deze manier kunnen we het netwerk- en geheugengebruik van onze applicatie optimaliseren.

2. Installatie

2.1. Voorbeeld van gegevensmodel

Laten we eens kijken naar ons voorbeeldgegevensmodel dat we in de voorbeelden zullen gebruiken.

Ten eerste maken we een School entiteit:

@Entity openbare klas School {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) lange privé-id; private String naam; @OneToMany (mappedBy = "school") privélijst studenten; // Getters en setters ...}

Elk School zal nul of meer hebben Leerlings:

@Entity openbare klasse Student {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) lange privé-id; private String naam; @ManyToOne particuliere school; // Getters en setters ...}

2.2. SQL-query's traceren

Bij het uitvoeren van onze voorbeelden moeten we controleren of insert / update-instructies inderdaad in batches worden verzonden. Helaas kunnen we uit Hibernate-logboekinstructies niet begrijpen of SQL-instructies in batches zijn of niet. Daarom gebruiken we een gegevensbronproxy om Hibernate / JPA SQL-instructies te traceren:

private statische klasse ProxyDataSourceInterceptor implementeert MethodInterceptor {private final DataSource dataSource; openbare ProxyDataSourceInterceptor (laatste DataSource dataSource) {this.dataSource = ProxyDataSourceBuilder.create (dataSource) .name ("Batch-Insert-Logger") .asJson (). countQuery (). logQueryToSysOut (). build (); } // Andere methodes... }

3. Standaardgedrag

Hibernate schakelt standaard geen batchverwerking in. Dit betekent dat het een afzonderlijke SQL-instructie verzendt voor elke invoeg- / updatebewerking:

@Transactional @Test openbare leegte whenNotConfigured_ThenSendsInsertsSeparately () {for (int i = 0; i <10; i ++) {School school = createSchool (i); entiteitManager.persist (school); } entityManager.flush (); }

Hier hebben we 10 volgehouden School entiteiten. Als we naar de querylogboeken kijken, kunnen we zien dat Hibernate elke invoeginstructie afzonderlijk verzendt:

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

Daarom moeten we Hibernate configureren om batchverwerking mogelijk te maken. Voor dit doeleinde, we zouden moeten gaan slaapstand.jdbc.batch_size eigenschap naar een getal groter dan 0.

Als we creëren EntityManager handmatig moeten we toevoegen slaapstand.jdbc.batch_size naar de Hibernate-eigenschappen:

openbare eigenschappen hibernateProperties () {Eigenschappen eigenschappen = nieuwe eigenschappen (); properties.put ("hibernate.jdbc.batch_size", "5"); // Andere eigenschappen ... retourneer eigenschappen; }

Als we Spring Boot gebruiken, kunnen we deze definiëren als een applicatie-eigenschap:

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

4. Batchinzet voor enkele tafel

4.1. Batch invoegen zonder expliciete spoeling

Laten we eerst kijken hoe we batchinvoegingen kunnen gebruiken als we te maken hebben met slechts één entiteitstype.

We gebruiken het vorige codevoorbeeld, maar deze keer is batchverwerking ingeschakeld:

@Transactional @Test openbare leegte whenInsertingSingleTypeOfEntity_thenCreatesSingleBatch () {voor (int i = 0; i <10; i ++) {School school = createSchool (i); entiteitManager.persist (school); }}

Hier hebben we volgehouden 10 School entiteiten. Als we naar de logboeken kijken, kunnen we verifiëren dat Hibernate invoeginstructies in batches verzendt:

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

Een belangrijk ding om hier te vermelden is het geheugengebruik. Wanneer we een entiteit persistent maken, slaat Hibernate deze op in de persistentiecontext. Als we bijvoorbeeld 100.000 entiteiten in één transactie behouden, hebben we uiteindelijk 100.000 entiteitsinstanties in het geheugen, wat mogelijk een OutOfMemoryException.

4.2. Batchinvoeging met expliciete spoeling

Nu gaan we bekijken hoe we het geheugengebruik tijdens batchbewerkingen kunnen optimaliseren. Laten we dieper ingaan op de rol van de persistentiecontext.

Allereerst slaat de persistentiecontext nieuw gemaakte entiteiten op en ook de gewijzigde entiteiten in het geheugen. Hibernate stuurt deze wijzigingen naar de database wanneer de transactie wordt gesynchroniseerd. Dit gebeurt meestal aan het einde van een transactie. Echter, roeping EntityManager.flush () activeert ook een transactiesynchronisatie.

Ten tweede dient de persistentiecontext als een entiteitscache, ook wel de cache van het eerste niveau genoemd. Om entiteiten in de persistentiecontext te wissen, kunnen we bellen EntityManager.clear ().

Dus om de geheugenbelasting tijdens batchverwerking te verminderen, kunnen we bellen EntityManager.flush () en EntityManager.clear () op onze applicatiecode, telkens wanneer de batchgrootte is bereikt:

@Transactional @Test openbare leegte whenFlushingAfterBatch_ThenClearsMemory () {voor (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entiteitManager.clear (); } School school = createSchool (i); entiteitManager.persist (school); }}

Hier spoelen we de entiteiten in de persistentiecontext, waardoor Hibernate query's naar de database verzendt. Door de persistentiecontext te wissen, verwijderen we bovendien de School entiteiten uit het geheugen. Het batchgedrag blijft hetzelfde.

5. Batchbijlage voor meerdere tafels

Laten we nu eens kijken hoe we batch-inserts kunnen configureren wanneer we te maken hebben met meerdere entiteitstypen in één transactie.

Als we de entiteiten van verschillende typen willen behouden, maakt Hibernate een andere batch voor elk entiteitstype. Dit is zo omdat er kan slechts één type entiteit in een enkele batch zijn.

Bovendien, aangezien Hibernate invoeginstructies verzamelt, wordt er een nieuwe batch gemaakt wanneer het een ander entiteitstype tegenkomt dan dat in de huidige batch. Dit is het geval, ook al is er al een batch voor dat entiteitstype:

@Transactional @Test openbare leegte whenThereAreMultipleEntities_ThenCreatesNewBatch () {voor (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entiteitManager.clear (); } School school = createSchool (i); entiteitManager.persist (school); Student firstStudent = createStudent (school); Student secondStudent = createStudent (school); entiteitManager.persist (firstStudent); entiteitManager.persist (secondStudent); }}

Hier voegen we een School en het twee toewijzen Leerlings en dit proces 10 keer herhalen.

In de logboeken zien we dat Hibernate verzendt School voeg verklaringen in verschillende batches van maat 1 in terwijl we slechts 2 batches van maat 5 verwachtten. Bovendien, Leerling invoeginstructies worden ook verzonden in verschillende batches van maat 2 in plaats van 4 batches van maat 5:

"batch": true, "querySize": 1, "batchSize": 1, "query": ["invoegen in school (naam, id) waarden (?,?)"], "params": [["School1" , "1"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["invoegen in waarden van leerling (naam, school_id, id) (?,?,?)"] , "params": [["Student-School1", "1", "2"], ["Student-School1", "1", "3"]] "batch": true, "querySize": 1, "batchSize": 1, "query": ["insert into school (name, id) values ​​(?,?)"], "params": [["School2", "4"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["invoegen in waarden van leerling (naam, school_id, id) (?,?,?)"], "params": [["Student-School2" , "4", "5"], ["Student-School2", "4", "6"]] "batch": true, "querySize": 1, "batchSize": 1, "query": [" invoegen in school (naam, id) waarden (?,?) "]," params ": [[" School3 "," 7 "]]" batch ": true," querySize ": 1," batchSize ": 2, "query": ["invoegen in waarden van leerling (naam, school_id, id) (?,?,?)"], "params": [["Student-School3", "7", "8"], [" Student-School3 "," 7 "," 9 "]] Andere logregels ...

Om alle invoeginstructies van hetzelfde entiteitstype te batchen, we moeten het slaapstand.order_inserts eigendom.

We kunnen de Hibernate-eigenschap handmatig configureren met EntityManagerFactory:

openbare eigenschappen hibernateProperties () {Eigenschappen eigenschappen = nieuwe eigenschappen (); properties.put ("hibernate.order_inserts", "true"); // Andere eigenschappen ... retourneer eigenschappen; }

Als we Spring Boot gebruiken, kunnen we de eigenschap configureren in application.properties:

spring.jpa.properties.hibernate.order_inserts = true

Na het toevoegen van deze eigenschap hebben we 1 batch voor School inserts en 2 batches voor Leerling inzetstukken:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["invoegen in school (naam, id) waarden (?,?)"], "params": [["School6" , "16"], ["School7", "19"], ["School8", "22"], ["School9", "25"], ["School10", "28"]] "batch": true, "querySize": 1, "batchSize": 5, "query": ["invoegen in leerling (naam, school_id, id) waarden (?,?,?)"], "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": [" invoegen in waarden voor student (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. Batchupdate

Laten we nu verder gaan met batchupdates. Net als bij batchinserts kunnen we verschillende update-statements groeperen en deze in één keer naar de database sturen.

Om dit mogelijk te maken, we zullen configureren slaapstand. order_updates en slaapstand.jdbc.batch_version_data eigendommen.

Als we onze EntityManagerFactory handmatig kunnen we de eigenschappen programmatisch instellen:

openbare eigenschappen hibernateProperties () {Eigenschappen eigenschappen = nieuwe eigenschappen (); properties.put ("hibernate.order_updates", "true"); properties.put ("hibernate.batch_versioned_data", "true"); // Andere eigenschappen ... retourneer eigenschappen; }

En als we Spring Boot gebruiken, voegen we ze gewoon toe aan application.properties:

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

Na het configureren van deze eigenschappen, zou Hibernate update-instructies in batches moeten groeperen:

@Transactional @Test public void whenUpdatingEntities_thenCreatesBatch () {TypedQuery schoolQuery = entityManager.createQuery ("SELECT s van School s", School.class); Lijst allSchools = schoolQuery.getResultList (); voor (School school: allSchools) {school.setName ("Updated_" + school.getName ()); }}

Hier hebben we schoolentiteiten bijgewerkt en Hibernate verzendt SQL-instructies in 2 batches van grootte 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": true, "querySize ": 1," batchSize ": 5," query ": [" update school set name =? Where id =? "]," Params ": [[" Updated_School6 "," 6 "], [" Updated_School7 "," 7 "], [" Updated_School8 "," 8 "], [" Updated_School9 "," 9 "], [" Updated_School10 "," 10 "]]

7. @ID kaart Generatiestrategie

Als we batching willen gebruiken voor inserts / updates, moeten we ons bewust zijn van de primaire sleutelgeneratiestrategie. Als onze entiteiten GenerationType.IDENTITY identifier generator, zal Hibernate in stilte batchinserts / updates uitschakelen.

Omdat entiteiten in onze voorbeelden GenerationType.SEQUENCE identifier generator, Hibernate maakt batchbewerkingen mogelijk:

@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) privé lang ID;

8. Samenvatting

In dit artikel hebben we gekeken naar batchinvoegingen en updates met behulp van Hibernate / JPA.

Bekijk de codevoorbeelden voor dit artikel op Github.