Java >> Java tutorial >  >> Tag >> hibernate

FlushMode i JPA og Hibernate – Hvad det er, og hvordan man ændrer det

FlushMode definerer, hvornår nye entiteter og dine ændringer på eksisterende bliver skrevet til databasen. Dette kan virke som en simpel og indlysende mekanisme. Men jeg erkendte i mine Q&A-opkald, at det ofte forårsager en vis forvirring, fordi Hibernate som standard ikke udfører en flush, når du kalder persist metode på din EntityManager , eller gem metode på dit Spring Data JPA-lager. Den udfører heller ikke kun en skylning i slutningen af ​​transaktionen, selvom dette er en vigtig og garanteret udløser af en skylleoperation.

Den specifikke skyllehåndtering afhænger af kombinationen af ​​den konfigurerede FlushMode , den type operationer, du udfører, og nogle Hibernate-specifikke optimeringer. Jeg vil forklare alt det i denne artikel.

FlushModes understøttet af JPA og Hibernate

JPA-specifikationen definerer kun FlushModeType s AUTO og COMMIT . Hibernate udvider dette med FlushModeType s ALTID og MANUEL . Lad os se nærmere på alle 4 tilstande.

FlushModeType.AUTO (JPA &Hibernate)

JPA-specifikationen definerer FlushModeType.AUTO som standard skylletilstand. Det fjerner persistenskonteksten i 2 situationer:

  • før transaktionen bliver forpligtet og
  • før du udfører en forespørgsel, der bruger enhver databasetabel, for hvilken din persistenskontekst indeholder eventuelle afventende ændringer.

At tømme persistenskonteksten, før transaktionen bliver forpligtet, burde være indlysende og ikke kræve nogen dybere forklaring. Men den 2. situation er lidt mere kompleks og kræver, at du ved, hvordan Hibernate bestemmer, hvilke databasetabeller en forespørgsel påvirker. Dette er baseret på det forespørgselsrum, jeg forklarede i en tidligere artikel.

For hver JPQL eller Criteria Query genererer Hibernate SQL-sætningen. Den ved derfor, hvilke databasetabeller der bruges i forespørgslen. Hibernate kan bruge det, når der udføres en snavset kontrol af alle entitetsobjekter i den aktuelle persistenskontekst. Hvis den finder en beskidt enhed, der er knyttet til en af ​​tabellerne, der henvises til af forespørgslen, skal den tømme disse ændringer til databasen.

Detektering af forespørgselsrummet

Her kan du se et simpelt eksempel, der fortsætter med en ny ChessPlayer enhed, før den vælger alle ChessTournament enheder i den første og ChessPlayer med fornavn Magnus i 2. forespørgsel.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

em.createQuery("SELECT t from ChessTournament t").getResultList();

Query q = em.createQuery("SELECT p FROM ChessPlayer p WHERE p.firstName = :firstName");
q.setParameter("firstName", "Magnus");
q.getResultList();

em.getTransaction().commit();
em.close();

Som du kan se i log-outputtet, fjerner Hibernate ikke den nye ChessPlayer enhed, før den udfører forespørgslen, der vælger alle ChessTournament enheder. Denne forespørgsel refererer ikke til ChessPlayer bord og den nye ChessPlayer vil ikke påvirke forespørgselsresultatet. Dvale kan derfor forsinke indsættelseserklæringens udførelse, hvilket kan skabe nogle ydeevnefordele.

11:56:14,076 DEBUG SQL:144 - select nextval ('player_seq')
11:56:14,085 DEBUG SQL:144 - select nextval ('player_seq')
11:56:14,188 DEBUG SQL:144 - select chesstourn0_.id as id1_2_, chesstourn0_.endDate as enddate2_2_, chesstourn0_.name as name3_2_, chesstourn0_.startDate as startdat4_2_, chesstourn0_.version as version5_2_ from ChessTournament chesstourn0_
11:56:14,213 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)
11:56:14,219 DEBUG SQL:144 - select chessplaye0_.id as id1_1_, chessplaye0_.birthDate as birthdat2_1_, chessplaye0_.firstName as firstnam3_1_, chessplaye0_.lastName as lastname4_1_, chessplaye0_.version as version5_1_ from ChessPlayer chessplaye0_ where chessplaye0_.firstName=?

Men det ændrer sig, når Hibernate vælger en ChessPlayer med fornavn Magnus. Den JPQL-forespørgsel refererer til ChessPlayer bord, og Hibernate ved ikke, om den nye ChessPlayer enhed vil påvirke forespørgselsresultatet. Den tømmer derfor entiteten og genererer en SQL INSERT-sætning til den, før den udfører forespørgslen.

Det bliver lidt mere komplekst, hvis du udfører en indbygget SQL-forespørgsel. Som jeg forklarede i min guide til Hibernates forespørgselsrum, kan Hibernate ikke bestemme, hvilke tabeller en indbygget SQL-forespørgsel bruger. Du bør derfor registrere forespørgselsrummet for hver indbygget forespørgsel. Ellers er Hibernate ikke i stand til at beslutte, om det skal tømme persistenskonteksten. Det fjerner derfor persistenskonteksten for at sikre, at forespørgslen returnerer de korrekte resultater.

Du kan se et eksempel på dette i følgende kodestykke. Denne gang fortsætter jeg med en ny ChessPlayer enhed og udfør 2 indbyggede SQL-forespørgsler, der vælger alle ChessTournament enheder. Til den 1. registrerer jeg Skakturneringen enhed som forespørgselsrum. Og for den 2. registrerer jeg ikke forespørgselsrummet.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

// with defined query space
Query q = em.createNativeQuery("SELECT * from ChessTournament", ChessTournament.class);
q.unwrap(SynchronizeableQuery.class).addSynchronizedEntityClass(ChessTournament.class);
q.getResultList();

// without query space
em.createNativeQuery("SELECT * from ChessTournament", ChessTournament.class).getResultList();

em.getTransaction().commit();
em.close();

Som du kan se i log-outputtet, udløser den første forespørgsel ikke en flush af den nye ChessPlayer enhed. Hibernate tjekkede forespørgselsrummet og vidste, at den nye ChessPlayer enhed er ikke relevant for denne indbyggede forespørgsel.

Men den 2. forespørgsel udløste en flush. Dette er den samme forespørgselserklæring som den forrige, men jeg registrerede ikke forespørgselsrummet. På grund af det vidste Hibernate ikke, om forespørgslen refererede til ChessPlayer bord og skulle tømme den nye enhed.

12:01:38,984 DEBUG SQL:144 - select nextval ('player_seq')
12:01:38,992 DEBUG SQL:144 - select nextval ('player_seq')
12:01:39,037 DEBUG SQL:144 - SELECT * from ChessTournament
12:01:39,058 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)
12:01:39,066 DEBUG SQL:144 - SELECT * from ChessTournament

FlushModeType.COMMIT (JPA &Hibernate)

FlushModeType.COMMIT kræver en flush, før transaktionen udføres, men definerer ikke, hvad der skal ske, før en forespørgsel udføres. Når du bruger Hibernate 5 eller 6, fjerner udførelse af en forespørgsel ikke nogen afventende ændringer.

Du kan se, at i det følgende eksempel fortsætter en ny ChessPlayer før du vælger alle ChessPlayer enheder fra databasen.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

List<ChessPlayer> players = em.createQuery("SELECT p from ChessPlayer p").getResultList();
for (ChessPlayer p : players) {
	log.info(p);
}

em.getTransaction().commit();
em.close();

JPQL-forespørgslen, der vælger alle ChessPlayer entiteter forårsager ikke en flush af den nyligt vedvarende ChessPlayer enhed. På grund af det er afspilleren ikke en del af forespørgselsresultatet.

12:14:17,117 DEBUG SQL:144 - select nextval ('player_seq')
12:14:17,125 DEBUG SQL:144 - select nextval ('player_seq')
12:14:17,225 DEBUG SQL:144 - select chessplaye0_.id as id1_1_, chessplaye0_.birthDate as birthdat2_1_, chessplaye0_.firstName as firstnam3_1_, chessplaye0_.lastName as lastname4_1_, chessplaye0_.version as version5_1_ from ChessPlayer chessplaye0_
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=1, firstName=Magnus, lastName=Carlsen, birthDate=1990-09-30, version=0]
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=2, firstName=Jorden, lastName=van Foreest, birthDate=1999-04-30, version=0]
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=3, firstName=Anish, lastName=Giri, birthDate=1994-06-28, version=0]
12:14:17,241  INFO TestSample:96 - ChessPlayer [id=4, firstName=Fabiano, lastName=Caruana, birthDate=1992-07-30, version=0]
12:14:17,249 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)

FlushModeType.ALWAYS (Hibernate)

FlushModeType.ALWAYS er Hibernate-specifik og beder Hibernate om at tømme persistenskonteksten, før en forespørgsel udføres. Ved at bruge denne tilstand kontrollerer Hibernate ikke, om skylningen er påkrævet, og håndterer alle typer forespørgsler på samme måde.

I det følgende eksempel fortsætter jeg med en ny Skakspiller enhed, før du vælger alle Skakturneringer enheder fra databasen.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

em.createQuery("SELECT t from ChessTournament t").getResultList();

em.getTransaction().commit();
em.close();

Den nye spiller ændrer ikke forespørgselsresultatet, og flush ville ikke være nødvendig. Men fordi jeg konfigurerede FlushModeType.ALWAYS , Dvale tømmer alligevel persistenskonteksten.

12:29:41,306 DEBUG SQL:144 - select nextval ('player_seq')
12:29:41,318 DEBUG SQL:144 - select nextval ('player_seq')
12:29:41,449 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)
12:29:41,459 DEBUG SQL:144 - select chesstourn0_.id as id1_2_, chesstourn0_.endDate as enddate2_2_, chesstourn0_.name as name3_2_, chesstourn0_.startDate as startdat4_2_, chesstourn0_.version as version5_2_ from ChessTournament chesstourn0_

FlushModeType.MANUAL (Dvaletilstand)

FlushModeType.MANUAL er den 2. dvale-specifikke tilstand. Den deaktiverer alle automatiske skylninger og kræver, at applikationen udløser skylningerne automatisk.

Jeg bruger dette i følgende eksempel, når jeg fortsætter med en ny ChessPlayer , ved at vælge alle Skakspillere enheder fra databasen og fjernelse af persistenskonteksten.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);

List<ChessPlayer> players = em.createQuery("SELECT p from ChessPlayer p").getResultList();
for (ChessPlayer p : players) {
	log.info(p);
}
		
em.flush();
em.getTransaction().commit();
em.close();

Jeg deaktiverede alle automatiske skylninger, og JPQL-forespørgslen forårsagede ikke længere en skylning af den nyligt vedvarende ChessPlayer enhed. På grund af det er afspilleren ikke en del af forespørgselsresultatet og bliver ikke tømt, før jeg kalder EntityManager.flush() metode.

14:50:16,552 DEBUG SQL:144 - select nextval ('player_seq')
14:50:16,559 DEBUG SQL:144 - select nextval ('player_seq')
14:50:16,652 DEBUG SQL:144 - select chessplaye0_.id as id1_1_, chessplaye0_.birthDate as birthdat2_1_, chessplaye0_.firstName as firstnam3_1_, chessplaye0_.lastName as lastname4_1_, chessplaye0_.version as version5_1_ from ChessPlayer chessplaye0_
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=1, firstName=Magnus, lastName=Carlsen, birthDate=1990-09-30, version=0]
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=2, firstName=Jorden, lastName=van Foreest, birthDate=1999-04-30, version=0]
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=3, firstName=Anish, lastName=Giri, birthDate=1994-06-28, version=0]
14:50:16,669  INFO TestSample:135 - ChessPlayer [id=4, firstName=Fabiano, lastName=Caruana, birthDate=1992-07-30, version=0]
14:50:16,678 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, id) values (?, ?, ?, ?, ?)

Jeg anbefaler ikke at bruge FlushModeType.MANUAL . Det skaber en høj risiko for, at du går glip af at tømme nogle ændringer i databasen, eller at nogle af dine forespørgsler ikke bruger de seneste ændringer.

Nogle hold forsøger at bruge det til at forbedre ydeevnen af ​​deres vedholdenhedslag. Men i næsten alle tilfælde forbedrer forsinkelse af skylleoperationen kun ydeevnen, hvis du implementerede dit persistenslag på den forkerte måde. Det skjuler kun problemer med ydeevnen i stedet for at løse det.

I betragtning af de høje risici er denne FlushMode introducerer, anbefaler jeg at løse disse ydeevneproblemer i stedet for at skjule dem. Dette kræver normalt ikke mere arbejde end at implementere og teste den manuelle skyllehåndtering.

Sådan konfigurerer du FlushMode

Du kan konfigurere skylletilstanden globalt eller indstille den for hver databaseforespørgsel. Dette giver dig mulighed for at definere en standard skylletilstand for din applikation og tilsidesætte den for en specifik forespørgsel.

Du kan indstille standard skylletilstand for din applikation ved at konfigurere egenskaben org.hibernate.flushMode i din persistence.xml fil. Som standard er FlushMode er indstillet til AUTO, og jeg anbefaler, at du IKKE ændre det.

<persistence>
    <persistence-unit name="my-persistence-unit">
        ...

        <properties>
            <property name="org.hibernate.flushMode" value="COMMIT"/>
			
			...
        </properties>
    </persistence-unit>
</persistence>

Den måde, du kan konfigurere en forespørgselsspecifik skylletilstand på, afhænger af FlushModeType du vil indstille. Hvis du vil bruge FlushModeTypes AUTO eller COMMIT, som er defineret af JPA-specifikationen, kan du kalde setFlushMode metode på din Forespørgsel eller TypedQuery grænseflade.

Query q = em.createQuery("SELECT p from ChessPlayer p");
q.setFlushMode(FlushModeType.COMMIT);
q.getResultList();

Og hvis du vil bruge en Hibernate-specifik skylletilstand, skal du bruge Hibernates Session for at oprette din forespørgsel og kalde dens setHibernateFlushMode metode.

Query q = em.createQuery("SELECT p from ChessPlayer p");
q.unwrap(org.hibernate.query.Query.class).setHibernateFlushMode(FlushMode.ALWAYS);
q.getResultList();

Udløser flush programmatisk

Som du allerede har set i afsnittet om FlushModeType.MANUAL , kan du udløse en flush programmatisk ved at kalde EntityManager.flush metode.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Query q = em.createQuery("SELECT p from ChessPlayer p");
q.setFlushMode(FlushModeType.COMMIT);
q.getResultList();

// trigger a flush		
em.flush();

em.getTransaction().commit();
em.close();

Hvis du bruger FlushModeType AUTO eller COMMIT, bør dette kun være nødvendigt, hvis du bruger en JPQL- eller Criteria-forespørgsel til at udføre en masseopdatering eller fjerne handlinger. Hibernates automatiske skyllehåndtering er lettere at bruge og mere effektiv i alle andre tilfælde.

Konklusion

FlushMode definerer, hvornår din persistensudbyder tømmer nye og ændrede entiteter til databasen.

Baseret på JPA-specifikationen kan den enten gøre det automatisk, før en forespørgsel udføres, og før transaktionen udføres (FlushModeType.AUTO ) eller kun før udførelsen af ​​transaktionen (FlushModeType.COMMIT ).

Hibernate understøtter 2 ekstra FlushModes, som du kan bruge til at tømme persistenskonteksten før hver forespørgsel (FlushModeType.ALWAYS )eller for at administrere skylningerne programmatisk og deaktivere alle automatiske skylninger (FlushModeType.MANUAL ).

Jeg anbefaler at bruge FlushMode Indtast .AUTO for alle persistenslag. Det undgår risikoen for, at du går glip af at tømme en afventende ændring, eller at en forespørgsel fungerer på forældede data. Og Hibernate giver sin egen optimering baseret på forespørgselsrummet for at undgå unødvendige flushes.


Java tag