Introduktion til OData med Olingo

1. Introduktion

Denne vejledning er en opfølgning på vores OData Protocol Guide, hvor vi har undersøgt det grundlæggende i OData-protokollen.

Nu ser vi, hvordan vi implementerer en simpel OData-tjeneste ved hjælp af Apache Olingo-biblioteket.

Dette bibliotek giver en ramme til at eksponere data ved hjælp af OData-protokollen, hvilket giver let, standardbaseret adgang til information, der ellers ville være låst væk i interne databaser.

2. Hvad er Olingo?

Olingo er en af ​​de "fremhævede" OData-implementeringer, der er tilgængelige for Java-miljøet - den anden er SDL OData Framework. Det vedligeholdes af Apache Foundation og består af tre hovedmoduler:

  • Java V2 - klient- og serverbiblioteker, der understøtter OData V2
  • Java V4 - serverbiblioteker, der understøtter OData V4
  • Javascript V4 - Javascript, kun klientbibliotek, der understøtter OData V4

I denne artikel dækker vi kun V2 Java-biblioteker på serversiden, som understøtter direkte integration med JPA. Den resulterende tjeneste understøtter CRUD-operationer og andre OData-protokolfunktioner, herunder bestilling, personsøgning og filtrering.

Olingo V4 håndterer på den anden side kun de lavere niveauer i protokollen, såsom indholdstypeforhandling og URL-parsing. Dette betyder, at det er op til os, udviklere, at kode alle detaljerede oplysninger om ting som generering af metadata, generere backend-forespørgsler baseret på URL-parametre osv.

Hvad JavaScript-klientbiblioteket angår, udelader vi det for nu, da OData er en HTTP-baseret protokol, kan vi bruge ethvert REST-bibliotek til at få adgang til det.

3. En Olingo Java V2-tjeneste

Lad os oprette en simpel OData-tjeneste med de to EntitySets, som vi har brugt i vores korte introduktion til selve protokollen. I sin kerne er Olingo V2 simpelthen et sæt JAX-RS-ressourcer, og som sådan er vi nødt til at levere den nødvendige infrastruktur for at kunne bruge den. Vi har nemlig brug for en JAX-RS-implementering og en kompatibel servletcontainer.

I dette eksempel Vi har valgt at bruge Spring Boot - da det giver en hurtig måde at skabe et passende miljø til at være vært for vores service. Vi bruger også Olingos JPA-adapter, som "taler" direkte til en bruger leveret EntityManager for at samle alle de data, der er nødvendige for at oprette OData'erne EntityDataModel.

Selvom det ikke er et strengt krav, forenkler JPA-adapteren i høj grad opgaven med at oprette vores service.

Udover standard Spring Boot-afhængigheder skal vi tilføje 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-processor-core 2.0.11 org.apache.olingo olingo-odata2 -jpa-processor-ref 2.0.11 org.eclipse.persistence eclipselink 

Den seneste version af disse biblioteker er tilgængelig på Mavens Central-arkiv:

  • olingo-odata2-kerne
  • olingo-odata2-jpa-processor-kerne
  • olingo-odata2-jpa-processor-ref

Vi har brug for disse undtagelser på denne liste, fordi Olingo har afhængigheder af EclipseLink som sin JPA-udbyder og også bruger en anden JAX-RS-version end Spring Boot.

3.1. Domæneklasser

Det første skridt til at implementere en JPA-baseret OData-tjeneste med Olingo er at oprette vores domæneenheder. I dette enkle eksempel opretter vi kun 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 udeladt} @Entity @Table (name = "car_model") offentlig klasse CarModel {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat Lang id; @NotNull privat strengnavn; @NotNull privat helår; @NotNull privat streng-sku; @ManyToOne (valgfri = falsk, hent = FetchType.LAZY) @JoinColumn (navn = "maker_fk") privat CarMaker-producent; // ... getters, setters og hashcode udeladt}

3.2. ODataJPAServiceFactory Implementering

Den nøglekomponent, vi skal levere til Olingo for at kunne betjene data fra et JPA-domæne, er en konkret implementering af en abstrakt klasse kaldet ODataJPAServiceFactory. Denne klasse skal udvides ODataServiceFactory og fungerer som en adapter mellem JPA og OData. Vi navngiver denne fabrik CarsODataJPAServiceFabrik, efter hovedemnet for vores domæne:

@Komponent offentlig klasse CarsODataJPAServiceFactory udvider ODataJPAServiceFactory {// andre metoder udeladt ... @ Override public ODataJPAContext initializeODataJPAContext () throw ODataJPARuntimeException {ODataJPAContext ctx = getODataJPAContext (); ODataContext octx = ctx.getODataContext (); HttpServletRequest anmodning = (HttpServletRequest) octx.getParameter (ODataContext.HTTP_SERVLET_REQUEST_OBJECT); EntityManager em = (EntityManager) anmodning .getAttribute (EntityManagerFilter.EM_REQUEST_ATTRIBUTE); ctx.setEntityManager (em); ctx.setPersistenceUnitName ("standard"); ctx.setContainerManaged (sand); returnere ctx; }} 

Olingo kalder initialisereJPAContext () metode, hvis denne klasse for at få en ny ODataJPAContext bruges til at håndtere hver OData-anmodning. Her bruger vi getODataJPAContext () metode fra basisklassen for at få en "almindelig" forekomst, som vi derefter foretager nogle tilpasninger.

Denne proces er noget indviklet, så lad os tegne en UML-sekvens for at visualisere, hvordan alt dette sker:

Bemærk, at vi med vilje bruger setEntityManager () i stedet for setEntityManagerFactory (). Vi kunne få en fra Spring, men hvis vi videregiver den til Olingo, vil den komme i konflikt med den måde, som Spring Boot håndterer sin livscyklus - især når det drejer sig om transaktioner.

Af denne grund vil vi ty til at passere en allerede eksisterende EntityManager eksempel og informer det om, at dets livscyklus styres eksternt. Den injicerede EntityManager forekomst kommer fra en attribut, der er tilgængelig på den aktuelle anmodning. Vi vil senere se, hvordan vi indstiller denne attribut.

3.3. Jersey Resource Registrering

Det næste trin er at registrere vores ServiceFabrik med Olingos runtime, og registrer Olingos indgangspunkt med JAX-RS runtime. Vi gør det inde i en ResourceConfig afledt klasse, hvor vi også definerer OData-stien, som vores service skal være / odata:

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

Olingo's leveres ODataApplication er en almindelig JAX-RS Ansøgning klasse, der registrerer et par udbydere ved hjælp af standard tilbagekald getClasses ().

Vi kan bruge alle undtagen ODataRootLocator klasse som den er. Denne ene er ansvarlig for at starte vores ODataJPAServiceFactory implementering ved hjælp af Java'er newInstance () metode. Men da vi ønsker, at Spring skal administrere det for os, er vi nødt til at erstatte det med en brugerdefineret locator.

Denne locator er en meget enkel JAX-RS-ressource, der udvider Olingos lager ODataRootLocator og det returnerer vores forårsledede ServiceFabrik når det er nødvendigt:

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

3.4. EntityManager Filter

Det sidste tilbageværende stykke til vores OData-service EntityManagerFilter. Dette filter injicerer en EntityManager i den aktuelle anmodning, så den er tilgængelig for ServiceFabrik. Det er en simpel JAX-RS @Udbyder klasse, der implementerer begge dele ContainerRequestFilter og ContainerResponseFilter grænseflader, så det kan håndtere transaktioner korrekt:

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

Den første filter() metode, kaldet i starten af ​​en ressourceanmodning, bruger den leverede EntityManagerFactory at skabe et nyt EntityManager eksempel, som derefter placeres under en attribut, så det senere kan gendannes af ServiceFabrik. Vi springer også GET-anmodninger over, da de ikke burde have nogen bivirkninger, og vi behøver derfor ikke en transaktion.

Sekundet filter() metode kaldes, når Olingo er færdig med at behandle anmodningen. Her kontrollerer vi også anmodningsmetoden og begår transaktionen, hvis det kræves.

3.5. Testning

Lad os teste vores implementering ved hjælp af simpelt krølle kommandoer. Den første, vi kan gøre, er at få tjenesterne $ metadata dokument:

curl // localhost: 8080 / odata / $ metadata

Som forventet indeholder dokumentet to typer - CarMaker og CarModel - og en forening. Lad os nu spille lidt mere med vores service og hente samlinger og enheder på øverste niveau:

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

Lad os nu teste en simpel forespørgsel, der returnerer alle Bilproducenter hvor navnet starter med 'B':

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

En mere komplet liste over eksempler på webadresser findes i vores artikel om OData Protocol Guide.

5. Konklusion

I denne artikel har vi set hvordan man opretter en simpel OData-tjeneste bakket op af et JPA-domæne ved hjælp af Olingo V2.

I skrivende stund er der et åbent emne om Olingos JIRA, der sporer værkerne på et JPA-modul til V4, men den sidste kommentar går tilbage til 2016. Der er også en tredjeparts open source JPA-adapter, der er vært på SAPs GitHub-lager, skønt det ikke er frigivet, ser det ud til at være mere komplet på dette tidspunkt end Olingos.

Som sædvanlig er al kode til denne artikel tilgængelig på GitHub.