Dvale på anden niveau-cache

1. Oversigt

En af fordelene ved databaseabstraktionslag som ORM-rammer (objekt-relationel kortlægning) er deres evne til at cache data transparent hentet fra den underliggende butik. Dette hjælper med at fjerne databaseadgangsomkostninger for hyppigt tilgængelige data.

Ydelsesgevinster kan være betydelige, hvis læse- / skriveforhold for cachelagret indhold er høje, især for enheder, der består af store objektgrafer.

I denne artikel undersøger vi dvale på andet niveau cache.

Vi forklarer nogle grundlæggende begreber, og som altid illustrerer vi alt med enkle eksempler. Vi bruger JPA og falder kun tilbage til Hibernate native API kun for de funktioner, der ikke er standardiserede i JPA.

2. Hvad er en anden niveau cache?

Som de fleste andre fuldt udstyrede ORM-rammer har Hibernate begrebet cache på første niveau. Det er en session scoped cache, der sikrer, at hver enhedsinstans kun indlæses en gang i den vedvarende sammenhæng.

Når sessionen er lukket, afsluttes cache på første niveau også. Dette er faktisk ønskeligt, da det gør det muligt for samtidige sessioner at arbejde med enhedsforekomster isoleret fra hinanden.

På den anden side er cache på andet niveau SessionFactory-scoped, hvilket betyder at den deles af alle sessioner oprettet med den samme sessionsfabrik. Når en enhedsforekomst bliver slået op af dens id (enten ved applikationslogik eller internt i dvale, f.eks. når den indlæser tilknytninger til den enhed fra andre enheder), og hvis cache-niveau på andet niveau er aktiveret for denne enhed, sker følgende:

  • Hvis en forekomst allerede findes i cache på første niveau, returneres den derfra
  • Hvis en forekomst ikke findes i cache på det første niveau, og den tilsvarende forekomsttilstand cachelagres i cache på andet niveau, hentes dataene derfra, og en forekomst samles og returneres
  • Ellers indlæses de nødvendige data fra databasen, og en instans samles og returneres

Når forekomsten er gemt i persistens-konteksten (cache på første niveau), returneres den derfra i alle efterfølgende opkald inden for den samme session, indtil sessionen lukkes, eller forekomsten fjernes manuelt fra persistens-konteksten. Også den indlæste forekomststilstand er gemt i L2-cache, hvis den ikke allerede var der.

3. Regionfabrik

Dvale-niveau cachelagring på andet niveau er designet til at være uvidende om den aktuelle cacheudbyder. Dvaletilstand skal kun forsynes med en implementering af org.hibernate.cache.spi.RegionFactory interface, der indkapsler alle detaljer, der er specifikke for aktuelle cacheudbydere. Dybest set fungerer det som en bro mellem dvale- og cacheudbydere.

I denne artikel vi bruger Ehcache som en cacheudbyder, som er en moden og udbredt cache. Du kan selvfølgelig vælge en hvilken som helst anden udbyder, så længe der er en implementering af en RegionFactory for det.

Vi tilføjer fabriksimplementeringen af ​​Ehcache-regionen til klassestien med følgende Maven-afhængighed:

 org. dvale dvale-ehcache 5.2.2.Final 

Se her for den nyeste version af dvale-ehcache. Men sørg for, at dvale-ehcache version er lig med dvaleversion, som du bruger i dit projekt, f.eks. hvis du bruger dvale-ehcache 5.2.2.Final som i dette eksempel, skal versionen af ​​dvale også være 5.2.2 Afsluttende.

Det dvale-ehcache artefakt har en afhængighed af selve Ehcache-implementeringen, som således også medtages transitvis i klassestien.

4. Aktivering af cache-niveau på andet niveau

Med de følgende to egenskaber fortæller vi dvale, at L2-caching er aktiveret, og vi giver det navnet på regionens fabriksklasse:

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

For eksempel i persistence.xml det ser ud som:

 ...   ... 

For at deaktivere caching på andet niveau (f.eks. Til fejlfinding) skal du blot indstille hibernate.cache.use_second_level_cache ejendom til falsk.

5. Gør en enhed cacheable

For at gøre en enhed berettiget til caching på andet niveau, vi kommenterer det med Hibernate-specifikt @ org.hibernate.annotations.Cache kommentar og angiv en cache-samtidighedsstrategi.

Nogle udviklere mener, at det er en god konvention at tilføje standarden @ javax.persistence.Cacheable kommentar (også selvom det ikke kræves af dvale), så en enhedsklasse-implementering kan se sådan ud:

@Entity @Cacheable @ org.hibernate.annotations.Cache (brug = CacheConcurrencyStrategy.READ_WRITE) offentlig klasse Foo {@Id @GeneratedValue (strategi = GenerationType.AUTO) @Column (navn = "ID") privat lang id; @Column (name = "NAME") privat strengnavn; // getters og setters}

For hver enhedsklasse bruger dvale en separat cache-region til at gemme tilstanden af ​​forekomster for den pågældende klasse. Regionens navn er det fuldt kvalificerede klassenavn.

For eksempel, Foo forekomster gemmes i en cache med navnet com.baeldung.hibernate.cache.model.Foo i Ehcache.

For at kontrollere, at caching fungerer, kan vi skrive en hurtig test som denne:

Foo foo = ny Foo (); fooService.create (foo); fooService.findOne (foo.getId ()); int størrelse = CacheManager.ALL_CACHE_MANAGERS.get (0) .getCache ("com.baeldung.hibernate.cache.model.Foo"). getSize (); assertThat (størrelse, størreThan (0));

Her bruger vi Ehcache API direkte til at bekræfte det com.baeldung.hibernate.cache.model.Foo cache er ikke tom, når vi har indlæst a Foo eksempel.

Du kan også aktivere logning af SQL genereret af dvale og påberåbe sig fooService.findOne (foo.getId ()) flere gange i testen for at kontrollere, at Vælg erklæring til indlæsning Foo udskrives kun én gang (første gang), hvilket betyder at enhedsforekomsten i efterfølgende opkald hentes fra cachen.

6. Cache-samtidighedsstrategi

Baseret på brugssager er vi fri til at vælge en af ​​følgende cache-samtidige strategier:

  • LÆS KUN: Bruges kun til enheder, der aldrig ændres (undtagelse kastes, hvis der gøres et forsøg på at opdatere en sådan enhed). Det er meget simpelt og performant. Meget velegnet til nogle statiske referencedata, der ikke ændres
  • NONSTRICT_READ_WRITE: Cache opdateres, efter at en transaktion, der har ændret de berørte data, er begået. Således er stærk konsistens ikke garanteret, og der er et lille tidsvindue, hvor uaktuelle data kan opnås fra cachen. Denne type strategi er velegnet til brugssager, der kan tåle eventuel konsistens
  • LÆSE SKRIVE: Denne strategi garanterer stærk konsistens, som den opnås ved at bruge 'bløde' låse: Når en cachelagret enhed opdateres, gemmes en blød lås også i cachen for den pågældende enhed, som frigives, når transaktionen er begået. Alle samtidige transaktioner, der får adgang til softlåste poster, henter de tilsvarende data direkte fra databasen
  • TRANSAKTIONEL: Cacheændringer udføres i distribuerede XA-transaktioner. En ændring i en cachelagret enhed er enten begået eller rullet tilbage i både database og cache i den samme XA-transaktion

7. Cache-styring

Hvis udløbs- og udsættelsespolitikker ikke er defineret, kan cachen vokse på ubestemt tid og til sidst forbruge al tilgængelig hukommelse. I de fleste tilfælde overlader dvale til cacheadministrationsopgaver som disse til cacheudbydere, da de faktisk er specifikke for hver cacheimplementering.

For eksempel kunne vi definere følgende Ehcache-konfiguration for at begrænse det maksimale antal cachelagrede Foo forekomster til 1000:

8. Opsamlingscache

Samlinger cachelagres ikke som standard, og vi skal udtrykkeligt markere dem som cache. For eksempel:

@Entity @Cacheable @ org.hibernate.annotations.Cache (brug = CacheConcurrencyStrategy.READ_WRITE) offentlig klasse Foo {... @Cacheable @ org.hibernate.annotations.Cache (brug = CacheConcurrencyStrategy.READ_WRITE) @OneToMany private samling barer; // getters og setters}

9. Intern repræsentation af cachelagret stat

Enheder gemmes ikke i cache på andet niveau som Java-forekomster, men snarere i deres adskilte (hydratiserede) tilstand:

  • Id (primær nøgle) er ikke gemt (den gemmes som en del af cache-nøglen)
  • Forbigående egenskaber lagres ikke
  • Samlinger gemmes ikke (se nedenfor for flere detaljer)
  • Ikke-tilknyttede ejendomsværdier gemmes i deres oprindelige form
  • Kun id (fremmed nøgle) er gemt til Til en foreninger

Dette viser det generelle dvale-niveau cache-design, hvor cache-modellen afspejler den underliggende relationsmodel, som er pladseffektiv og gør det let at holde de to synkroniserede.

9.1. Intern repræsentation af cachelagrede samlinger

Vi nævnte allerede, at vi eksplicit skal angive, at en samling (OneToMany eller MangeToMany tilknytning) er cache, ellers er det ikke cachelagret.

Faktisk gemmer dvaletilstand samlinger i separate cache-regioner, en for hver samling. Regionens navn er et fuldt kvalificeret klassenavn plus navnet på samlingsejendommen, for eksempel: com.baeldung.hibernate.cache.model.Foo.barer. Dette giver os fleksibiliteten til at definere separate cache-parametre for samlinger, f.eks. bortvisning / udløbspolitik.

Det er også vigtigt at nævne, at kun id'er for enheder, der er indeholdt i en samling, gemmes for hver samlingspost, hvilket betyder, at det i de fleste tilfælde også er en god ide at gøre de indeholdte enheder også cacheable.

10. Cache-ugyldighed for HQL DML-stilforespørgsler og indfødte forespørgsler

Når det kommer til DML-stil HQL (indsæt, opdatering og slet HQL-udsagn), dvale er i stand til at bestemme, hvilke enheder der påvirkes af sådanne operationer:

entityManager.createQuery ("opdater Foo-sæt ... hvor…"). executeUpdate ();

I dette tilfælde fjernes alle Foo-forekomster fra L2-cache, mens andet cachelagret indhold forbliver uændret.

Men når det kommer til indbyggede SQL DML-udsagn, kan dvale ikke gætte, hvad der opdateres, så det annullerer hele cache på andet niveau:

session.createNativeQuery ("opdater FOO-sæt… hvor…"). executeUpdate ();

Dette er sandsynligvis ikke, hvad du vil have! Løsningen er at fortælle dvale, hvilke enheder der er berørt af indfødte DML-udsagn, så det kun kan udvise poster, der er relateret til Foo enheder:

Forespørgsel nativeQuery = entityManager.createNativeQuery ("opdater FOO-sæt ... hvor ..."); nativeQuery.unwrap (org.hibernate.SQLQuery.class) .addSynchronizedEntityClass (Foo.class); nativeQuery.executeUpdate ();

Vi er også faldet tilbage til dvaleindfødte SQLQuery API, da denne funktion (endnu) ikke er defineret i JPA.

Bemærk, at ovenstående kun gælder for DML-udsagn (indsæt, opdatering, slet og oprindelige funktion / procedureopkald). Hjemmehørende Vælg forespørgsler ugyldiggør ikke cache.

11. Forespørgselscache

Resultater af HQL-forespørgsler kan også cachelagres. Dette er nyttigt, hvis du ofte udfører en forespørgsel på enheder, der sjældent ændres.

For at aktivere forespørgselscache skal du indstille værdien af hibernate.cache.use_query_cache ejendom til rigtigt:

hibernate.cache.use_query_cache = sand

Derefter skal du for hver forespørgsel udtrykkeligt angive, at forespørgslen kan caches (via en org.hibernate.cacheable tip til forespørgsel):

entityManager.createQuery ("vælg f fra Foo f") .setHint ("org.hibernate.cacheable", sand) .getResultList ();

11.1. Bedste fremgangsmåder til forespørgselcache

Her er nogle retningslinjer og bedste praksis relateret til caching af forespørgsler:

  • Som det er tilfældet med samlinger, cachelagres kun id'er for enheder, der returneres som et resultat af en cacherbar forespørgsel, så det anbefales kraftigt, at cache på andet niveau er aktiveret for sådanne enheder.
  • Der er en cacheindgang pr. Hver kombination af forespørgselsparameterværdier (bind variabler) for hver forespørgsel, så forespørgsler, som du forventer, at mange forskellige kombinationer af parameterværdier ikke er gode kandidater til caching.
  • Forespørgsler, der involverer enhedsklasser, for hvilke der er hyppige ændringer i databasen, er heller ikke gode kandidater til caching, fordi de ugyldiggøres, hver gang der er en ændring relateret til en af ​​de enheder, der er klassificeret, der deltager i forespørgslen, uanset om de ændrede forekomster er cache som en del af forespørgselsresultatet eller ej.
  • Som standard gemmes alle forespørgselscache-resultater i org.hibernate.cache.internal.StandardQueryCache område. Som med caching af enheder / samlinger kan du tilpasse cache-parametre til denne region for at definere bortkastnings- og udløbspolitikker efter dine behov. For hver forespørgsel kan du også angive et brugerdefineret regionnavn for at give forskellige indstillinger til forskellige forespørgsler.
  • For alle tabeller, der forespørges som en del af cacherbare forespørgsler, holder dvaletilstand tidsstempler for sidste opdatering i en separat region med navnet org.hibernate.cache.spi.UpdateTimestampsCache. At være opmærksom på denne region er meget vigtig, hvis du bruger cache-forespørgsel, fordi Hibernate bruger den til at kontrollere, at cachelagrede forespørgselsresultater ikke er forældede. Posterne i denne cache må ikke fjernes / udløbet, så længe der er cachelagrede forespørgselsresultater for de tilsvarende tabeller i forespørgselsresultateregioner. Det er bedst at slå automatisk bortkastning og udløb fra for denne cache-region, da den alligevel ikke bruger meget hukommelse.

12. Konklusion

I denne artikel kiggede vi på, hvordan man opsætter dvale på andet niveau cache. Vi så, at det er ret nemt at konfigurere og bruge, da Hibernate gør alt det tunge løft bag kulisserne, hvilket gør brug af andet niveau cache gennemsigtig for applikationens forretningslogik.

Implementeringen af ​​denne Hibernate Cache-vejledning på andet niveau er tilgængelig på Github. Dette er et Maven-baseret projekt, så det skal være let at importere og køre som det er.