@NaturalId – En god måte å opprettholde naturlige IDer med Hibernate?
I den virkelige verden har de fleste objekter en naturlig identifikator. Typiske eksempler er ISBN-nummeret til en bok, et selskaps skatteidentifikator eller en persons personnummer. Du kan selvfølgelig bruke disse identifikatorene som primærnøkler. Men oftest er det en bedre idé å generere numeriske surrogatnøkler. De er enklere å administrere, og de fleste rammeverk kan håndtere dem mer effektivt enn mer komplekse naturlige identifikatorer.
En naturlig identifikator identifiserer likevel en databasepost og et objekt i den virkelige verden. Mange brukssaker bruker dem i stedet for en kunstig surrogatnøkkel. Det er derfor god praksis å modellere dem som unike nøkler i databasen. Hibernate lar deg også modellere dem som en naturlig identifikator for en enhet og gir en ekstra API for å hente dem fra databasen.
Definer et attributt som en naturlig id
Det eneste du trenger å gjøre for å modellere et attributt er en naturlig id, er å legge til @NaturalId merknad. Du kan se et eksempel i følgende kodebit. isbn nummeret til en bok er en typisk naturlig id. Den identifiserer posten, men er mer kompleks enn primærnøkkelen id . ID attributt er en surrogatnøkkel og blir generert av Hibernate.
@Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = “id”, updatable = false, nullable = false) private Long id; @NaturalId private String isbn; … }
Naturlige IDer er uforanderlige som standard, og du bør ikke angi settermetoder for dem. Hvis du trenger mutbar, naturlig identifikator, må du angi mutable attributtet til @NaturalId kommentar til true .
Få en enhet med dens naturlige ID
Hibernates Session-grensesnitt gir metodene byNaturalId og bySimpleNaturalId å lese en enhet ved dens naturlige identifikator fra databasen. La oss ta en titt på byNaturalId metode først.
Følgende kodebit viser hvordan du kan bruke denne metoden for å få en enhet etter dens naturlige ID. Du må oppgi klassen eller navnet på enheten som en parameter til byNaturalId metode.
Anropet til brukeren metoden gir navnet på det naturlige ID-attributtet og verdien. Hvis den naturlige IDen består av flere attributter, må du kalle denne metoden flere ganger for å definere hver del av IDen. I dette eksemplet bruker jeg JPA-metamodellen for å få navnet på isbn attributt.
Etter at du har oppgitt verdien av den naturlige ID-en, kan du ringe load metode for å få enheten identifisert av den. Hibernate tilbyr også andre alternativer for å få enheten som jeg viser deg i den følgende delen.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); Book b = session.byNaturalId(Book.class).using(Book_.isbn.getName(), “978-0321356680”).load();
En ting som overrasket meg da jeg brukte denne API-en for første gang, var antallet søk Hibernate utfører. Jeg forventet at Hibernate oppretter 1 SQL-setning for å lese enheten. Men det er ikke tilfelle. Hibernate utfører 2 spørringer, som du kan se i loggmeldinger nedenfor. Den første spørringen velger den primære for den gitte naturlige ID-en, og den andre bruker den for å hente enheten.
Årsaken til denne tilnærmingen er mest sannsynlig at Hibernate trenger primærnøkkelverdien internt for å sjekke cachen på 1. og 2. nivå. I de fleste tilfeller bør ikke denne tilleggsforespørselen ha stor innvirkning på ytelsen. Hibernate bufrer også den naturlige ID-en til primærnøkkelmapping for økten og kan lagre den i cachen på 2. nivå slik at det ikke er nødvendig å hente den igjen.
06:14:40,705 DEBUG SQL:92 – select book_.id as id1_0_ from Book book_ where book_.isbn=? 06:14:40,715 DEBUG SQL:92 – select book0_.id as id1_0_0_, book0_.isbn as isbn2_0_0_, book0_.publishingDate as publishi3_0_0_, book0_.title as title4_0_0_, book0_.version as version5_0_0_ from Book book0_ where book0_.id=?
bySimpleNaturalId metoden gir et praktisk alternativ for å velge enheter med enkle naturlige IDer som består av bare ett attributt. Som du kan se i følgende kodebit, kan du oppgi den naturlige ID-verdien direkte til last metoden og trenger ikke å kalle bruker metode.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); Book b = session.bySimpleNaturalId(Book.class).load(“978-0321356680”);
3 alternativer for å hente enheten
Som jeg forklarte tidligere, tilbyr Hibernate 3 forskjellige alternativer for å hente en enhet med dens naturlige ID fra databasen:
load() | Får en referanse til den initialiserte enheten. |
loadOptional() | Får en referanse til den initialiserte enheten eller null og pakker den inn i en Valgfri . Jeg forklarte Hibernates valgfrie støtte mer detaljert i Hvordan bruke Java 8s valgfrie med Hibernate. |
getReference() | Får en referanse til enheten eller en uinitialisert proxy. |
Låser
Grensesnittene NaturalIdLoadAccess og SimpleNaturalIdLoadAccess gi med(LockOptions-lås) metode. Du kjenner det sannsynligvis fra IdentifierLoadAccess grensesnitt som blir returnert av Session.byId(Class-entity) metode. Du kan bruke denne metoden til å definere hvilken låsemodus Hibernate skal bruke for spørringen.
I den følgende kodebiten bruker jeg denne metoden til å sette en skrivelås på den valgte enheten.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); Book b = session.bySimpleNaturalId(Book.class).with(LockOptions.UPGRADE).load(“978-0321356680”);
Du kan se i den loggede SQL-setningen at Hibernate la til "for oppdatering" i spørringen. Dette nøkkelordet utløser skrivelåsen i PostgreSQL-databasen jeg bruker for dette eksemplet.
06:19:34,055 DEBUG SQL:92 – select book_.id as id1_0_ from Book book_ where book_.isbn=? 06:19:34,128 DEBUG SQL:92 – select book0_.id as id1_0_0_, book0_.isbn as isbn2_0_0_, book0_.publishingDate as publishi3_0_0_, book0_.title as title4_0_0_, book0_.version as version5_0_0_ from Book book0_ where book0_.id=? for update
Caching
Som jeg forklarte i begynnelsen, bufrer Hibernate den naturlige ID-en til primærnøkkelmapping for hver økt. Du kan se et eksempel på det i følgende kodebit og de tilhørende loggmeldingene.
Jeg laster først inn boken enhet med id 1 fra databasen og skriv en loggmelding. I neste trinn laster jeg inn den samme enheten med dens naturlige identifikator.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); session.byId(Book.class).load(1L); log.info(“Get book by id”); Book b = session.bySimpleNaturalId(Book.class).load(“978-0321356680”);
Som du kan se i loggmeldingene, utfører Hibernate en select-setning for å få boken enhet med id 1. Men den kjører ikke en annen setning for å få den med sin naturlige ID. Hibernate la til primærnøkkelen til naturlig ID-tilordning til økten da jeg lastet inn enheten med id . Når jeg deretter laster inn enheten med dens naturlige ID, får Hibernate primærnøkkeltilordningen og enheten fra cachen på 1. nivå.
06:20:39,767 DEBUG SQL:92 – select book0_.id as id1_0_0_, book0_.isbn as isbn2_0_0_, book0_.publishingDate as publishi3_0_0_, book0_.title as title4_0_0_, book0_.version as version5_0_0_ from Book book0_ where book0_.id=? 06:20:39,785 INFO TestHibernateNaturalId:78 – Read book by id 06:20:39,788 INFO TestHibernateNaturalId:81 – Book title: Effective Java
Konklusjon
Å velge enheter etter dens naturlige identifikator er et vanlig brukstilfelle. Hibernates proprietære API gir en enkel og komfortabel måte å gjøre det på. Den ekstra select-erklæringen for å få primærnøkkelen for den oppgitte naturlige ID-en kommer som en overraskelse i begynnelsen. Men dette bør ikke være et ytelsesproblem, hvis du tenker på at du vanligvis legger til en databaseindeks i den naturlige identifikatorkolonnen. Så snart Hibernate kjenner til kartleggingen mellom den naturlige IDen og primærnøkkelen, kan den bruke de kjente optimaliserings- og hurtigbuffermekanismene.