Introduksjon til OData med Olingo

1. Introduksjon

Denne opplæringen er en oppfølging av vår OData Protocol Guide, hvor vi har utforsket det grunnleggende i OData-protokollen.

Nå skal vi se hvordan du implementerer en enkel OData-tjeneste ved hjelp av Apache Olingo-biblioteket.

Dette biblioteket gir et rammeverk for å eksponere data ved hjelp av OData-protokollen, og gir dermed enkel, standardbasert tilgang til informasjon som ellers ville blitt låst bort i interne databaser.

2. Hva er Olingo?

Olingo er en av de "utvalgte" OData-implementeringene som er tilgjengelige for Java-miljøet - den andre er SDL OData Framework. Den vedlikeholdes av Apache Foundation og består av tre hovedmoduler:

  • Java V2 - klient- og serverbiblioteker som støtter OData V2
  • Java V4 - serverbiblioteker som støtter OData V4
  • Javascript V4 - Javascript, kun klientbibliotek som støtter OData V4

I denne artikkelen vil vi bare dekke V2 Java-biblioteker på serversiden, som støtter direkte integrasjon med JPA. Den resulterende tjenesten støtter CRUD-operasjoner og andre OData-protokollfunksjoner, inkludert bestilling, personsøk og filtrering.

Olingo V4 håndterer derimot bare aspekter av lavere nivå av protokollen, for eksempel innholdstypeforhandling og URL-parsing. Dette betyr at det vil være opp til oss, utviklere, å kode alle detaljerte detaljer om ting som generering av metadata, generere backend-spørsmål basert på URL-parametere osv.

Når det gjelder JavaScript-klientbiblioteket, forlater vi det for nå, siden OData er en HTTP-basert protokoll, kan vi bruke hvilket som helst REST-bibliotek for å få tilgang til det.

3. En Olingo Java V2-tjeneste

La oss lage en enkel OData-tjeneste med de to EntitySets som vi har brukt i vår korte introduksjon til selve protokollen. I sin kjerne er Olingo V2 ganske enkelt et sett med JAX-RS-ressurser, og derfor må vi tilby den nødvendige infrastrukturen for å kunne bruke den. Vi trenger nemlig en JAX-RS-implementering og en kompatibel servletcontainer.

For dette eksemplet, vi har valgt å bruke Spring Boot - siden det gir en rask måte å skape et passende miljø for å være vert for tjenesten vår. Vi bruker også Olingos JPA-adapter, som "snakker" direkte til en bruker levert EntityManager for å samle alle dataene som trengs for å opprette OData-ene EntityDataModel.

Selv om det ikke er et strengt krav, forenkler JPA-adapteren i stor grad oppgaven med å opprette tjenesten vår.

I tillegg til vanlige Spring Boot-avhengigheter, må vi legge til et par Olingos krukker:

 org.apache.olingo olingo-odata2-core 2.0.11 javax.ws.rs javax.ws.rs-api org.apache.olingo olingo-odata2-jpa-prosessor-core 2.0.11 org.apache.olingo olingo-odata2 -jpa-prosessor-ref 2.0.11 org.eclipse.persistence eclipselink 

Den siste versjonen av disse bibliotekene er tilgjengelig på Mavens Central repository:

  • olingo-odata2-kjerne
  • olingo-odata2-jpa-prosessor-kjerne
  • olingo-odata2-jpa-prosessor-ref

Vi trenger disse unntakene i denne listen fordi Olingo har avhengigheter av EclipseLink som sin JPA-leverandør, og bruker også en annen JAX-RS-versjon enn Spring Boot.

3.1. Domeneklasser

Det første trinnet for å implementere en JPA-basert OData-tjeneste med Olingo er å opprette domenenhetene våre. I dette enkle eksemplet lager vi bare to klasser - CarMaker og CarModel - med et enkelt-til-mange forhold:

@Entity @Table (name = "car_maker") offentlig klasse CarMaker {@Id @GeneratedValue (strategi = GenerationType.IDENTITY) privat Lang id; @NotNull privat strengnavn; @OneToMany (mappedBy = "maker", orphanRemoval = true, cascade = CascadeType.ALL) private List-modeller; // ... getters, setters og hashcode utelatt} @Entity @Table (name = "car_model") public class CarModel {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @NotNull privat strengnavn; @NotNull privat Hele året; @NotNull private String sku; @ManyToOne (valgfritt = false, fetch = FetchType.LAZY) @JoinColumn (name = "maker_fk") privat CarMaker maker; // ... getters, setters og hashcode utelatt}

3.2. ODataJPAServiceFactory Gjennomføring

Nøkkelkomponenten vi trenger å gi Olingo for å kunne betjene data fra et JPA-domene, er en konkret implementering av en abstrakt klasse kalt ODataJPAServiceFactory. Denne klassen skal utvides ODataServiceFactory og fungerer som adapter mellom JPA og OData. Vi kaller denne fabrikken CarsODataJPAServiceFactory, etter hovedtemaet for domenet vårt:

@Component public class CarsODataJPAServiceFactory utvider ODataJPAServiceFactory {// andre metoder utelatt ... @Override public ODataJPAContext initializeODataJPAContext () kaster ODataJPARuntimeException {ODataJPAContext ctx = getODataJPAContext (); ODataContext octx = ctx.getODataContext (); HttpServletRequest request = (HttpServletRequest) octx.getParameter (ODataContext.HTTP_SERVLET_REQUEST_OBJECT); EntityManager em = (EntityManager) forespørsel .getAttribute (EntityManagerFilter.EM_REQUEST_ATTRIBUTE); ctx.setEntityManager (em); ctx.setPersistenceUnitName ("standard"); ctx.setContainerManaged (true); returnere ctx; }} 

Olingo kaller initialisereJPAContext () metode hvis denne klassen for å få en ny ODataJPAContext brukes til å håndtere hver OData-forespørsel. Her bruker vi getODataJPAContext () metode fra baseklassen for å få en "vanlig" forekomst som vi deretter gjør noen tilpasninger.

Denne prosessen er noe innviklet, så la oss tegne en UML-sekvens for å visualisere hvordan alt dette skjer:

Merk at vi med vilje bruker setEntityManager () i stedet for setEntityManagerFactory (). Vi kan få en fra våren, men hvis vi sender den til Olingo, vil den komme i konflikt med måten Spring Boot håndterer livssyklusen på - spesielt når du arbeider med transaksjoner.

Av denne grunn vil vi ty til å passere en allerede eksisterende EntityManager forekomst og informer den om at livssyklusen er administrert eksternt. Den injiserte EntityManager forekomst kommer fra et attributt som er tilgjengelig etter gjeldende forespørsel. Vi vil senere se hvordan du angir dette attributtet.

3.3. Jersey ressursregistrering

Neste trinn er å registrere vår ServiceFactory med Olingos kjøretid og registrer Olingos inngangspunkt med JAX-RS kjøretid. Vi gjør det inne i ResourceConfig avledet klasse, der vi også definerer OData-banen for tjenesten vår / odata:

@Component @ApplicationPath ("/ odata") offentlig klasse JerseyConfig utvider ResourceConfig {public JerseyConfig (CarsODataJPAServiceFactory serviceFactory, EntityManagerFactory emf) {ODataApplication app = new ODataApplication (); app .getClasses () .forEach (c -> {if (! ODataRootLocator.class.isAssignableFrom (c)) {register (c);}}); registrer deg (nye CarsRootLocator (serviceFactory)); registrer deg (ny EntityManagerFilter (emf)); } // ... andre metoder utelatt}

Olingo har gitt ODataApplication er en vanlig JAX-RS applikasjon klasse som registrerer noen få leverandører ved hjelp av standard tilbakeringing getClasses ().

Vi kan bruke alle bortsett fra ODataRootLocator klasse som den er. Denne spesielle er ansvarlig for å sette i gang vår ODataJPAServiceFactory implementering ved hjelp av Java-er newInstance () metode. Men siden vi vil at Spring skal administrere det for oss, må vi erstatte det med en tilpasset lokaliseringsenhet.

Denne locatoren er en veldig enkel JAX-RS-ressurs som utvider Olingos lager ODataRootLocator og det returnerer vår vårstyrte ServiceFactory når det trengs:

@Path ("/") offentlig klasse CarsRootLocator utvider ODataRootLocator {private CarsODataJPAServiceFactory serviceFactory; public CarsRootLocator (CarsODataJPAServiceFactory serviceFactory) {this.serviceFactory = serviceFactory; } @ Override offentlige ODataServiceFactory getServiceFactory () {returner this.serviceFactory; }} 

3.4. EntityManager Filter

Det siste gjenværende stykket for OData-tjenesten vår EntityManagerFilter. Dette filteret injiserer en EntityManager i den gjeldende forespørselen, så den er tilgjengelig for ServiceFactory. Det er en enkel JAX-RS @Forsørger klasse som implementerer begge deler ContainerRequestFilter og ContainerResponseFilter grensesnitt, slik at den kan håndtere transaksjoner på riktig måte:

@Provider public static class EntityManagerFilter implementerer ContainerRequestFilter, ContainerResponseFilter {public static final String EM_REQUEST_ATTRIBUTE = EntityManagerFilter.class.getName () + "_ENTITY_MANAGER"; privat slutt EntityManagerFactory emf; @Context private HttpServletRequest httpRequest; offentlig EntityManagerFilter (EntityManagerFactory emf) {this.emf = emf; } @Override public void filter (ContainerRequestContext ctx) kaster IOException {EntityManager em = this.emf.createEntityManager (); httpRequest.setAttribute (EM_REQUEST_ATTRIBUTE, em); if (! "GET" .equalsIgnoreCase (ctx.getMethod ())) {em.getTransaction (). begin (); }} @Override public void filter (ContainerRequestContext requestContext, ContainerResponseContext responseContext) kaster IOException {EntityManager em = (EntityManager) httpRequest.getAttribute (EM_REQUEST_ATTRIBUTE); if (! "GET" .equalsIgnoreCase (requestContext.getMethod ())) {EntityTransaction t = em.getTransaction (); hvis (t.isActive () &&! t.getRollbackOnly ()) {t.commit (); }} em.close (); }} 

Den første filter() metoden, kalt ved starten av en ressursforespørsel, bruker den angitte EntityManagerFactory for å skape et nytt EntityManager eksempel, som deretter settes under et attributt, slik at det senere kan gjenopprettes av ServiceFactory. Vi hopper også over GET-forespørsler siden det ikke burde ha noen bivirkninger, og slik at vi ikke trenger en transaksjon.

Den andre filter() metoden kalles etter at Olingo er ferdig med å behandle forespørselen. Her sjekker vi også forespørselsmetoden og utfører transaksjonen om nødvendig.

3.5. Testing

La oss teste implementeringen vår ved hjelp av enkel krølle kommandoer. Det første vi kan gjøre er å få tjenestene $ metadata dokument:

krøll // localhost: 8080 / odata / $ metadata

Som forventet inneholder dokumentet to typer - CarMaker og CarModel - og en forening. La oss nå leke litt mer med tjenesten vår og hente samlinger og enheter på toppnivå:

krøll // localhost: 8080 / odata / CarMakers krølle // localhost: 8080 / odata / CarModels krøll // localhost: 8080 / odata / CarMakers (1) krøll // localhost: 8080 / odata / CarModels (1) krøll // localhost : 8080 / odata / CarModels (1) / CarMakerDetails 

La oss nå teste et enkelt spørsmål som returnerer alle CarMakers hvor navnet begynner med ‘B’:

krøll // localhost: 8080 / odata / CarMakers? $ filter = startswith (Navn, 'B') 

En mer fullstendig liste over eksempler på nettadresser er tilgjengelig i vår artikkel om OData Protocol Guide.

5. Konklusjon

I denne artikkelen har vi sett hvordan du lager en enkel OData-tjeneste støttet av et JPA-domene ved hjelp av Olingo V2.

I skrivende stund er det et åpent tema på Olingos JIRA som sporer verkene på en JPA-modul for V4, men den siste kommentaren dateres tilbake til 2016. Det er også en tredjeparts JPA-adapter med åpen kildekode som er vert på SAPs GitHub-arkiv, som, selv om det ikke er utgitt, ser det ut til å være mer fullstendig på dette punktet enn Olingos.

Som vanlig er all kode for denne artikkelen tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found