Slaapstand tweede niveau cache

1. Overzicht

Een van de voordelen van database-abstractielagen zoals ORM-frameworks (object-relationele mapping) is hun mogelijkheid om gegevens transparant te cachen opgehaald uit de onderliggende winkel. Dit helpt de kosten voor databasetoegang voor vaak geraadpleegde gegevens te elimineren.

Prestatieverbeteringen kunnen aanzienlijk zijn als de lees / schrijf-verhoudingen van gecachte inhoud hoog zijn, vooral voor entiteiten die bestaan ​​uit grote objectgrafieken.

In dit artikel onderzoeken we de Hibernate-cache op het tweede niveau.

We leggen enkele basisconcepten uit en zoals altijd illustreren we alles met eenvoudige voorbeelden. We gebruiken JPA en vallen alleen terug op de Hibernate native API voor die functies die niet gestandaardiseerd zijn in JPA.

2. Wat is een cache op het tweede niveau?

Zoals de meeste andere volledig uitgeruste ORM-frameworks, heeft Hibernate het concept van cache op het eerste niveau. Het is een cache met een sessiebereik die ervoor zorgt dat elke entiteitsinstantie slechts één keer wordt geladen in de persistente context.

Zodra de sessie is gesloten, wordt de cache op het eerste niveau ook beëindigd. Dit is eigenlijk wenselijk, omdat het het mogelijk maakt dat gelijktijdige sessies werken met entiteitsinstanties die los van elkaar staan.

Aan de andere kant is cache op het tweede niveau SessionFactory-scoped, wat betekent dat het wordt gedeeld door alle sessies die met dezelfde sessiefabriek zijn gemaakt. Wanneer een entiteitsinstantie wordt opgezocht op basis van zijn ID (hetzij door toepassingslogica of intern door Hibernate, bijv. wanneer het koppelingen naar die entiteit laadt vanuit andere entiteiten), en als caching op het tweede niveau is ingeschakeld voor die entiteit, gebeurt het volgende:

  • Als er al een instantie aanwezig is in de cache van het eerste niveau, wordt deze vanaf daar geretourneerd
  • Als een instantie niet wordt gevonden in de cache van het eerste niveau en de corresponderende instantiestatus wordt in de cache opgeslagen in de cache van het tweede niveau, worden de gegevens van daaruit opgehaald en wordt een instantie samengesteld en geretourneerd
  • Anders worden de benodigde gegevens uit de database geladen en wordt een exemplaar samengesteld en geretourneerd

Zodra het exemplaar is opgeslagen in de persistentiecontext (cache van het eerste niveau), wordt het van daaruit teruggestuurd in alle volgende oproepen binnen dezelfde sessie totdat de sessie wordt gesloten of het exemplaar handmatig wordt verwijderd uit de persistentiecontext. Ook wordt de geladen instantiestatus opgeslagen in de L2-cache als deze er nog niet was.

3. Regio Fabriek

Hibernate caching op het tweede niveau is ontworpen om niet op de hoogte te zijn van de daadwerkelijk gebruikte cacheprovider. Hibernate hoeft alleen te worden voorzien van een implementatie van het org.hibernate.cache.spi.RegionFactory interface die alle details bevat die specifiek zijn voor daadwerkelijke cacheproviders. In feite fungeert het als een brug tussen Hibernate- en cacheproviders.

In dit artikel we gebruiken Ehcache als cacheprovider, wat een volwassen en veel gebruikte cache is. U kunt natuurlijk elke andere provider kiezen, zolang er een implementatie is van een RegionFactory ervoor.

We voegen de fabrieksimplementatie van de Ehcache-regio toe aan het klassenpad met de volgende Maven-afhankelijkheid:

 org.hibernate hibernate-ehcache 5.2.2.Final 

Kijk hier voor de laatste versie van slaapstand-ehcache. Zorg er echter voor dat slaapstand-ehcache versie is gelijk aan de Hibernate-versie die u in uw project gebruikt, bijv. als je gebruikt slaapstand-ehcache 5.2.2.Final zoals in dit voorbeeld, dan zou de versie van Hibernate ook moeten zijn 5.2.2.Finale.

De slaapstand-ehcache artefact is afhankelijk van de Ehcache-implementatie zelf, die dus ook tijdelijk in het klassenpad wordt opgenomen.

4. Caching op het tweede niveau inschakelen

Met de volgende twee eigenschappen vertellen we Hibernate dat L2-caching is ingeschakeld en geven we het de naam van de region factory class:

hibernate.cache.use_second_level_cache = echte hibernate.cache.region.factory_class = org.hibernate.cache.ehcache.EhCacheRegionFactory 

Bijvoorbeeld in persistence.xml het zou eruit zien als:

 ...   ... 

Om caching op het tweede niveau uit te schakelen (bijvoorbeeld voor foutopsporingsdoeleinden), stelt u gewoon in slaapstand.cache.use_second_level_cache eigenschap naar false.

5. Een entiteit cacheerbaar maken

Om zo te maak een entiteit in aanmerking voor caching op het tweede niveau, we annoteren het met Hibernate-specifiek @ org.hibernate.annotations.Cache annotatie en specificeer een cache-gelijktijdigheidsstrategie.

Sommige ontwikkelaars zijn van mening dat het een goede afspraak is om de standaard toe te voegen @ javax.persistence.Cacheable annotatie ook (hoewel niet vereist door Hibernate), dus een implementatie van een entiteitsklasse kan er als volgt uitzien:

@Entity @Cacheable @ org.hibernate.annotations.Cache (gebruik = CacheConcurrencyStrategy.READ_WRITE) openbare klasse Foo {@Id @GeneratedValue (strategie = GenerationType.AUTO) @Column (name = "ID") privé lange id; @Column (name = "NAME") private String naam; // getters en setters}

Voor elke entiteitsklasse gebruikt Hibernate een afzonderlijk cachegebied om de status van instanties voor die klasse op te slaan. De regionaam is de volledig gekwalificeerde klassenaam.

Bijvoorbeeld, Foo instanties worden opgeslagen in een cache met de naam com.baeldung.hibernate.cache.model.Foo in Ehcache.

Om te controleren of caching werkt, kunnen we een snelle test schrijven zoals deze:

Foo foo = nieuwe Foo (); fooService.create (foo); fooService.findOne (foo.getId ()); int size = CacheManager.ALL_CACHE_MANAGERS.get (0) .getCache ("com.baeldung.hibernate.cache.model.Foo"). getSize (); assertThat (size, groterThan (0));

Hier gebruiken we Ehcache API rechtstreeks om dat te verifiëren com.baeldung.hibernate.cache.model.Foo cache is niet leeg nadat we een Foo voorbeeld.

U kunt ook logboekregistratie inschakelen van SQL gegenereerd door Hibernate en aanroepen fooService.findOne (foo.getId ()) meerdere keren in de test om te controleren of de selecteer verklaring voor laden Foo wordt slechts één keer afgedrukt (de eerste keer), wat betekent dat bij volgende oproepen de entiteitsinstantie wordt opgehaald uit de cache.

6. Cache-gelijktijdigheidsstrategie

Op basis van gebruiksscenario's zijn we vrij om een ​​van de volgende gelijktijdigheidsstrategieën voor cache te kiezen:

  • ALLEEN LEZEN: Wordt alleen gebruikt voor entiteiten die nooit veranderen (er wordt een uitzondering gegenereerd als een poging wordt gedaan om een ​​dergelijke entiteit bij te werken). Het is heel eenvoudig en performant. Zeer geschikt voor enkele statische referentiegegevens die niet veranderen
  • NONSTRICT_READ_WRITE: Cache wordt bijgewerkt nadat een transactie die de betrokken gegevens heeft gewijzigd, is vastgelegd. Een sterke consistentie is dus niet gegarandeerd en er is een klein tijdvenster waarin verouderde gegevens uit de cache kunnen worden verkregen. Dit soort strategie is geschikt voor gebruikssituaties die eventuele consistentie kunnen tolereren
  • LEZEN SCHRIJVEN: Deze strategie garandeert een sterke consistentie die wordt bereikt door 'zachte' vergrendelingen te gebruiken: wanneer een gecachte entiteit wordt bijgewerkt, wordt er ook een zachte vergrendeling opgeslagen in de cache voor die entiteit, die wordt vrijgegeven nadat de transactie is vastgelegd. Alle gelijktijdige transacties die toegang hebben tot soft-locked-vermeldingen, halen de bijbehorende gegevens rechtstreeks uit de database
  • TRANSACTIONEEL: Cachewijzigingen worden uitgevoerd in gedistribueerde XA-transacties. Een wijziging in een gecachte entiteit wordt ofwel vastgelegd of teruggedraaid in zowel de database als de cache in dezelfde XA-transactie

7. Cachebeheer

Als er geen beleid voor verloop en verwijdering is gedefinieerd, kan de cache voor onbepaalde tijd groeien en uiteindelijk al het beschikbare geheugen in beslag nemen. In de meeste gevallen laat Hibernate cachebeheertaken zoals deze over aan cacheproviders, aangezien deze inderdaad specifiek zijn voor elke cache-implementatie.

We zouden bijvoorbeeld de volgende Ehcache-configuratie kunnen definiëren om het maximale aantal cachegeheugens te beperken Foo instanties tot 1000:

8. Collectiecache

Verzamelingen worden standaard niet in de cache opgeslagen en we moeten ze expliciet als cachegeheugen markeren. Bijvoorbeeld:

@Entity @Cacheable @ org.hibernate.annotations.Cache (gebruik = CacheConcurrencyStrategy.READ_WRITE) openbare klasse Foo {... @Cacheable @ org.hibernate.annotations.Cache (gebruik = CacheConcurrencyStrategy.READ_WRITE) @OneToMany privéverzamelingsbalken; // getters en setters}

9. Interne vertegenwoordiging van de staat in de cache

Entiteiten worden niet opgeslagen in cache op het tweede niveau als Java-instanties, maar in hun gedemonteerde (gehydrateerde) staat:

  • Id (primaire sleutel) wordt niet opgeslagen (het wordt opgeslagen als onderdeel van de cachesleutel)
  • Tijdelijke eigenschappen worden niet opgeslagen
  • Collecties worden niet opgeslagen (zie hieronder voor meer details)
  • Waarden van niet-associatie-eigenschappen worden in hun oorspronkelijke vorm opgeslagen
  • Alleen id (externe sleutel) wordt opgeslagen voor Tot een verenigingen

Dit toont het algemene Hibernate-cache-ontwerp op het tweede niveau waarin het cachemodel het onderliggende relationele model weerspiegelt, dat ruimtebesparend is en het gemakkelijk maakt om de twee gesynchroniseerd te houden.

9.1. Interne weergave van verzamelingen in de cache

We gaven al aan dat we expliciet moeten aangeven dat een collectie (Een te veel of Veel te veel associatie) is cacheerbaar, anders wordt het niet in de cache opgeslagen.

In feite slaat Hibernate verzamelingen op in afzonderlijke cachegebieden, één voor elke verzameling. De naam van de regio is een volledig gekwalificeerde klassenaam plus de naam van de eigenschap collectie, bijvoorbeeld: com.baeldung.hibernate.cache.model.Foo.bars. Dit geeft ons de flexibiliteit om afzonderlijke cacheparameters te definiëren voor verzamelingen, bijv. uitzetting / expiratie beleid.

Het is ook belangrijk om te vermelden dat alleen ID's van entiteiten in een collectie in de cache worden opgeslagen voor elke collectie-item, wat betekent dat het in de meeste gevallen een goed idee is om de ingesloten entiteiten ook cacheerbaar te maken.

10. Cache ongeldig maken voor HQL DML-stijl query's en native query's

Als het gaat om HQL in DML-stijl (invoegen, bijwerken en verwijderen HQL-statements), kan Hibernate bepalen welke entiteiten worden beïnvloed door dergelijke bewerkingen:

entityManager.createQuery ("update Foo set… waar…"). executeUpdate ();

In dit geval worden alle Foo-instanties verwijderd uit de L2-cache, terwijl andere gecachte inhoud ongewijzigd blijft.

Als het echter gaat om native SQL DML-instructies, kan Hibernate niet raden wat er wordt bijgewerkt, dus het maakt de volledige cache van het tweede niveau ongeldig:

session.createNativeQuery ("update FOO set… waar…"). executeUpdate ();

Dit is waarschijnlijk niet wat je wilt! De oplossing is om Hibernate te vertellen welke entiteiten worden beïnvloed door native DML-instructies, zodat het alleen items kan verwijderen die verband houden met Foo entiteiten:

Query nativeQuery = entityManager.createNativeQuery ("update FOO set ... waar ..."); nativeQuery.unwrap (org.hibernate.SQLQuery.class) .addSynchronizedEntityClass (Foo.class); nativeQuery.executeUpdate ();

We moeten ook terugvallen op native Hibernate SQLQuery API, aangezien deze functie (nog) niet is gedefinieerd in JPA.

Merk op dat het bovenstaande alleen van toepassing is op DML-instructies (invoegen, bijwerken, verwijderen en native functie / procedure-oproepen). Inheems selecteer query's maken de cache niet ongeldig.

11. Query-cache

Resultaten van HQL-query's kunnen ook in de cache worden opgeslagen. Dit is handig als u regelmatig een query uitvoert op entiteiten die zelden veranderen.

Om querycache in te schakelen, stelt u de waarde in van slaapstand.cache.use_query_cache eigendom aan waar:

hibernate.cache.use_query_cache = true

Vervolgens moet je voor elke query expliciet aangeven dat de query cacheerbaar is (via een org.hibernate.cacheable vraaghint):

entiteitManager.createQuery ("select f from Foo f") .setHint ("org.hibernate.cacheable", true) .getResultList ();

11.1. Best practices voor querycache

Hier zijn er een paar richtlijnen en best practices met betrekking tot het cachen van query's:

  • Net als bij verzamelingen, worden alleen ID's van entiteiten die worden geretourneerd als resultaat van een cachebare query in de cache opgeslagen, dus het wordt sterk aanbevolen om cache op het tweede niveau in te schakelen voor dergelijke entiteiten.
  • Er is één cache-item voor elke combinatie van queryparameterwaarden (bindvariabelen) voor elke query, dus query's waarvan u veel verschillende combinaties van parameterwaarden verwacht, zijn geen goede kandidaten voor caching.
  • Query's waarbij entiteitsklassen betrokken zijn waarvoor regelmatig wijzigingen in de database optreden, zijn ook geen goede kandidaten voor caching, omdat ze ongeldig worden gemaakt wanneer er een wijziging is met betrekking tot een van de geclassificeerde entiteit die aan de query deelneemt, ongeacht of de gewijzigde instanties dat wel zijn. in de cache opgeslagen als onderdeel van het zoekresultaat of niet.
  • Standaard worden alle querycacheresultaten opgeslagen in org.hibernate.cache.internal.StandardQueryCache regio. Net als bij het cachen van entiteiten / verzamelingen, kunt u cacheparameters voor deze regio aanpassen om beleid voor uitzetting en verval te definiëren op basis van uw behoeften. Voor elke query kunt u ook een aangepaste regionaam opgeven om verschillende instellingen voor verschillende query's op te geven.
  • Voor alle tabellen die worden opgevraagd als onderdeel van cachebare query's, bewaart Hibernate de tijdstempels van de laatste update in een aparte regio met de naam org.hibernate.cache.spi.UpdateTimestampsCache. Het is erg belangrijk dat u zich bewust bent van deze regio als u querycaching gebruikt, omdat Hibernate dit gebruikt om te controleren of de queryresultaten in de cache niet verouderd zijn. De vermeldingen in deze cache mogen niet worden verwijderd / vervallen zolang er queryresultaten in de cache zijn voor de overeenkomstige tabellen in regio's met queryresultaten. Het is het beste om automatisch verwijderen en verlopen uit te schakelen voor dit cachegebied, aangezien het toch niet veel geheugen verbruikt.

12. Conclusie

In dit artikel hebben we gekeken naar het instellen van de Hibernate-cache op het tweede niveau. We zagen dat het vrij eenvoudig te configureren en te gebruiken is, aangezien Hibernate al het zware werk achter de schermen doet, waardoor cachegebruik op het tweede niveau transparant wordt voor de bedrijfslogica van de applicatie.

De implementatie van deze Hibernate Second-Level Cache Tutorial is beschikbaar op Github. Dit is een op Maven gebaseerd project, dus het zou gemakkelijk moeten kunnen worden geïmporteerd en uitgevoerd zoals het is.