Skupno umetanje / ažuriranje pomoću Hibernate / JPA

1. Pregled

U ovom ćemo uputstvu pogledati kako možemo skupno umetnuti ili ažurirati entitete pomoću Hibernate / JPA.

Batching nam omogućuje slanje grupe SQL izraza u bazu podataka u jednom mrežnom pozivu. Na taj način možemo optimizirati upotrebu mreže i memorije naše aplikacije.

2. Postavljanje

2.1. Uzorak podataka

Pogledajmo naš model podataka koji ćemo koristiti u primjerima.

Prvo ćemo stvoriti Škola entitet:

@ Entity javna škola {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) private long id; privatni naziv niza; @OneToMany (mappedBy = "school") privatni popis učenika; // Dobavljači i postavljači ...}

Svaki Škola imat će nulu ili više Students:

@ Entity javni razred student {@Id @GeneratedValue (strategy = GenerationType.SEQUENCE) private long id; privatni naziv niza; @ManyToOne privatna školska škola; // Dobavljači i postavljači ...}

2.2. Praćenje SQL upita

Tijekom izvođenja naših primjera morat ćemo provjeriti jesu li izrazi za umetanje / ažuriranje doista poslani u skupinama. Nažalost, iz hibernatskih zapisa dnevnika ne možemo razumjeti jesu li SQL izrazi grupirani ili ne. Zbog toga ćemo koristiti proxy izvora podataka za praćenje Hibernate / JPA SQL izjava:

privatna statička klasa ProxyDataSourceInterceptor implementira MethodInterceptor {private final DataSource dataSource; javni ProxyDataSourceInterceptor (konačni DataSource dataSource) {this.dataSource = ProxyDataSourceBuilder.create (dataSource) .name ("Batch-Insert-Logger") .asJson (). countQuery (). logQueryToSysOut (). } // Ostale metode ...}

3. Zadano ponašanje

Hibernate prema zadanim postavkama ne omogućuje grupiranje. To znači da će poslati zasebni SQL izraz za svaku operaciju umetanja / ažuriranja:

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

Evo, ustrajali smo 10 Škola entiteta. Ako pogledamo zapisnike upita, možemo vidjeti da Hibernate šalje svaku izjavu za umetanje zasebno:

"querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School1", "1"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School2", "2"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School3", "3"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School4", "4"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School5", "5"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School6", "6"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School7", "7"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School8", "8"]] "querySize": 1, "batchSize": 0, "query": ["umetnuti u školske (ime, id) vrijednosti (?,?)"], " params ": [[" School9 "," 9 "]]" querySize ": 1," batchSize ": 0," query ": [" umetnite u školske (ime, id) vrijednosti (?,?) "]," parametar ": [[" School10 "," 10 "]]

Stoga bismo trebali konfigurirati Hibernate da omogući batching. Za ovu svrhu, trebali bismo postaviti hibernate.jdbc.batch_size svojstvo na broj veći od 0.

Ako stvaramo EntityManager ručno, trebali bismo dodati hibernate.jdbc.batch_size svojstvima hibernacije:

javna svojstva hibernateProperties () {Svojstva svojstva = nova svojstva (); svojstva.put ("hibernate.jdbc.batch_size", "5"); // Ostala svojstva ... return svojstva; }

Ako koristimo Spring Boot, možemo ga definirati kao svojstvo aplikacije:

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

4. Serijski uložak za jedan stol

4.1. Skupno umetanje bez eksplicitnog ispiranja

Pogledajmo prvo kako možemo koristiti batch umetke kada imamo posla samo s jednom vrstom entiteta.

Upotrijebit ćemo prethodni uzorak koda, ali ovaj je put omogućeno grupiranje:

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

Ovdje smo ustrajali 10 Škola entiteta. Kada pogledamo zapisnike, možemo provjeriti da Hibernate šalje izjave za umetanje u serijama:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School1" , "1"], ["School2", "2"], ["School3", "3"], ["School4", "4"], ["School5", "5"]] "batch": true, "querySize": 1, "batchSize": 5, "query": ["ubaci u školu (ime, id) vrijednosti (?,?)"], "params": [["School6", "6" ], ["School7", "7"], ["School8", "8"], ["School9", "9"], ["School10", "10"]]

Ovdje je važno napomenuti potrošnju memorije. Kada ustrajemo u entitetu, Hibernate ga pohranjuje u kontekst postojanosti. Na primjer, ako ustrajemo sa 100 000 entiteta u jednoj transakciji, na kraju ćemo imati 100 000 instanci entiteta u memoriji, što može uzrokovati OutOfMemoryException.

4.2. Skupno umetanje s eksplicitnim ispiranjem

Sada ćemo pogledati kako možemo optimizirati upotrebu memorije tijekom batch operacija. Zarobimo duboko u ulogu konteksta ustrajnosti.

Prije svega, kontekst postojanosti pohranjuje novostvorene entitete, ali i one modificirane u memoriju. Hibernate šalje ove promjene u bazu podataka kada se transakcija sinkronizira. To se općenito događa na kraju transakcije. Međutim, pozivajući EntityManager.flush () također pokreće sinkronizaciju transakcije.

Drugo, kontekst postojanosti služi kao predmemorija entiteta, pa se tako naziva i predmemorija prve razine. Da očistimo entitete u kontekstu postojanosti, možemo nazvati EntityManager.clear ().

Dakle, da bismo smanjili opterećenje memorije tijekom doziranja, možemo nazvati EntityManager.flush () i EntityManager.clear () na našem kodu aplikacije, kad god se postigne veličina serije:

@Transactional @Test javna praznina whenFlushingAfterBatch_ThenClearsMemory () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } Školska škola = createSchool (i); entityManager.persist (škola); }}

Ovdje ispiremo entitete u kontekstu postojanosti, čime Hibernate šalje upite u bazu podataka. Nadalje, uklanjanjem konteksta postojanosti uklanjamo Škola entiteti iz sjećanja. Ponašanje serije ostat će isto.

5. Serijski uložak za više tablica

Sada da vidimo kako možemo konfigurirati skupne umetke kada imamo posla s više vrsta entiteta u jednoj transakciji.

Kada želimo zadržati entitete nekoliko vrsta, Hibernate stvara različitu skupinu za svaku vrstu entiteta. Ovo je zbog u jednoj seriji može biti samo jedna vrsta entiteta.

Uz to, dok Hibernate prikuplja izjave za umetanje, kad god naiđe na tip entiteta koji se razlikuje od tipa u trenutnoj skupini, on stvara novi paket. To je slučaj iako već postoji serija za taj tip entiteta:

@Transactional @Test javna praznina whenThereAreMultipleEntities_ThenCreatesNewBatch () {for (int i = 0; i 0 && i% BATCH_SIZE == 0) {entityManager.flush (); entityManager.clear (); } Školska škola = createSchool (i); entityManager.persist (škola); Student firstStudent = createStudent (škola); Učenik secondStudent = createStudent (škola); entityManager.persist (firstStudent); entityManager.persist (secondStudent); }}

Ovdje ubacujemo a Škola i dodijelivši mu dvije Students i ponavljanje ovog postupka 10 puta.

U zapisnicima vidimo da Hibernate šalje Škola umetnite izjave u nekoliko serija veličine 1 dok smo očekivali samo 2 serije veličine 5. Štoviše, Student izjave za umetanje također se šalju u nekoliko serija veličine 2 umjesto u 4 serije veličine 5:

"batch": true, "querySize": 1, "batchSize": 1, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School1" , "1"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["umetnite u vrijednosti učenika (ime, škola_id, id) (?,?,?)"] , "params": [["Student-School1", "1", "2"], ["Student-School1", "1", "3"]] "batch": true, "querySize": 1, "batchSize": 1, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School2", "4"]] "batch": true, "querySize": 1, "batchSize": 2, "query": ["umetnite u vrijednosti učenika (ime, škola_id, id) (?,?,?)"], "params": [["Student-School2" , "4", "5"], ["Student-School2", "4", "6"]] "batch": true, "querySize": 1, "batchSize": 1, "query": [" umetnuti u školske (ime, id) vrijednosti (?,?) "]," params ": [[" School3 "," 7 "]]" batch ": true," querySize ": 1," batchSize ": 2, "upit": ["umetni u vrijednost učenika (ime, škola_id, id) (?,?,?)"], "params": [["učenik-škola3", "7", "8"], [" Student-School3 "," 7 "," 9 "]] Ostale linije dnevnika ...

Da biste grupirali sve umetnute izjave iste vrste entiteta, trebali bismo konfigurirati hibernate.order_inserts imovine.

Svojstvo Hibernate možemo ručno konfigurirati pomoću EntityManagerFactory:

javna svojstva hibernateProperties () {Svojstva svojstva = nova svojstva (); svojstva.put ("hibernate.order_inserts", "true"); // Ostala svojstva ... return svojstva; }

Ako koristimo Spring Boot, svojstvo možemo konfigurirati u application.properties:

spring.jpa.properties.hibernate.order_inserts = true

Nakon dodavanja ovog svojstva, imat ćemo 1 paket za Škola umetci i 2 serije za Student umetci:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["umetnuti u školu (ime, id) vrijednosti (?,?)"], "params": [["School6" , "16"], ["School7", "19"], ["School8", "22"], ["School9", "25"], ["School10", "28"]] "batch": true, "querySize": 1, "batchSize": 5, "query": ["umetni u vrijednost učenika (ime, škola_id, id) (?,?,?)"], "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": [" umetnite u vrijednosti učenika (ime, škola_id, id) (?,?,?) "]," params ": [[" Student-School8 "," 22 "," 24 "], [" Student-School9 "," 25 "," 26 "], [" Student-School9 "," 25 "," 27 "], [" Student-School10 "," 28 "," 29 "], [" Student-School10 "," 28 " , "30"]]

6. Skupno ažuriranje

Sada krenimo na serijska ažuriranja. Slično skupnim umetcima, možemo grupirati nekoliko izjava o ažuriranju i poslati ih u bazu podataka u jednom potezu.

Da biste to omogućili, konfigurirat ćemo hibernate.order_updates i hibernate.jdbc.batch_versioned_data Svojstva.

Ako stvaramo svoje EntityManagerFactory ručno, svojstva možemo postaviti programski:

javna svojstva hibernateProperties () {Svojstva svojstva = nova svojstva (); svojstva.put ("hibernate.order_updates", "true"); svojstva.put ("hibernate.batch_versioned_data", "true"); // Ostala svojstva ... return svojstva; }

A ako koristimo Spring Boot, samo ćemo ih dodati aplikaciji.properties:

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

Nakon konfiguriranja ovih svojstava, Hibernate bi trebao grupirati izjave o ažuriranju u serijama:

@Transactional @Test javna praznina whenUpdatingEntities_thenCreatesBatch () {TypedQuery schoolQuery = entityManager.createQuery ("SELECT s from School s", School.class); Navedi sveSchools = schoolQuery.getResultList (); za (Školska škola: sveŠkole) {school.setName ("Ažurirano_" + school.getName ()); }}

Ovdje smo ažurirali školske cjeline i Hibernate šalje SQL izjave u 2 serije veličine 5:

"batch": true, "querySize": 1, "batchSize": 5, "query": ["update ime školskog skupa =? 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 ": [" ažuriranje naziva školskog skupa =? Where id =? "]," Params ": [[" Updated_School6 "," 6 "], [" Updated_School7 "," 7 "], [" Ažurirana_Škola8 "," 8 "], [" Ažurirana_Škola9 "," 9 "], [" Ažurirana_Škola10 "," 10 "]]

7. @Iskaznica Strategija generacije

Kada želimo koristiti batching za umetanje / ažuriranje, trebali bismo biti svjesni strategije generiranja primarnog ključa. Ako naši entiteti koriste GenerationType.IDENTITY identifikatora, Hibernate će tiho onemogućiti umetanje / ažuriranje serije.

Budući da entiteti u našim primjerima koriste GenerationType.SEQUENCE generator identifikatora, Hibernate omogućuje batch operacije:

@Id @GeneratedValue (strategija = GenerationType.SEQUENCE) private long id;

8. Sažetak

U ovom smo članku pregledali umetanja i ažuriranja serije pomoću Hibernate / JPA.

Pogledajte uzorke koda za ovaj članak na Githubu.