Uvod u OData s Olingom

1. Uvod

Ovaj je vodič nastavak našeg Vodiča za protokol OData, gdje smo istražili osnove protokola OData.

Sada ćemo vidjeti kako implementirati jednostavnu OData uslugu pomoću Apache Olingo knjižnice.

Ova knjižnica pruža okvir za izlaganje podataka pomoću OData protokola, omogućujući tako jednostavan pristup informacijama na temelju standarda koji bi inače bili zaključani u internim bazama podataka.

2. Što je Olingo?

Olingo je jedna od “istaknutih” OData implementacija dostupnih za Java okruženje - drugi je SDL OData Framework. Održava ga Apache Foundation, a sastoji se od tri glavna modula:

  • Java V2 - klijentske i poslužiteljske knjižnice koje podržavaju OData V2
  • Java V4 - poslužiteljske knjižnice koje podržavaju OData V4
  • Javascript V4 - Javascript, knjižnica samo za klijenta koja podržava OData V4

U ovom ćemo članku pokriti samo Java biblioteke na poslužiteljskoj strani V2, koje podržavaju izravnu integraciju s JPA. Rezultirajuća usluga podržava CRUD operacije i druge značajke OData protokola, uključujući naručivanje, straničenje i filtriranje.

S druge strane, Olingo V4 obrađuje samo aspekte niže razine protokola, poput pregovora o sadržaju i raščlanjivanja URL-a. To znači da ćemo na nama, razvojnim programerima, kodirati sve sitne detalje u vezi sa stvarima poput generiranja metapodataka, generiranja pozadinskih upita na temelju parametara URL-a itd.

Što se tiče JavaScript klijentske knjižnice, zasad je izostavljamo jer, budući da je OData protokol zasnovan na HTTP-u, možemo joj koristiti bilo koju REST knjižnicu da bismo joj pristupili.

3. Usluga Olingo Java V2

Stvorimo jednostavnu OData uslugu s njih dvoje EntitySetkoje smo koristili u kratkom uvodu u sam protokol. U svojoj osnovi, Olingo V2 je jednostavno skup JAX-RS resursa i kao takav, trebamo osigurati potrebnu infrastrukturu da bismo je mogli koristiti. Trebaju nam, naime, JAX-RS implementacija i kompatibilni spremnik servleta.

Za ovaj primjer, odlučili smo se za korištenje Spring Boota - jer pruža brz način stvaranja prikladnog okruženja za smještaj naše usluge. Također ćemo upotrijebiti Olingov JPA adapter koji "razgovara" izravno s korisnikom EntityManager kako bi se prikupili svi podaci potrebni za stvaranje OData EntityDataModel.

Iako nije strog zahtjev, uključujući JPA adapter uvelike pojednostavljuje zadatak stvaranja naše usluge.

Osim standardnih ovisnosti Spring Boot-a, trebamo dodati i nekoliko Olingo-ovih staklenki:

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

Najnovija verzija tih knjižnica dostupna je u Mavenovom središnjem spremištu:

  • olingo-odata2-jezgra
  • olingo-odata2-jpa-procesor-jezgra
  • olingo-odata2-jpa-procesor-ref

Ta nam izuzeća trebaju na ovom popisu jer Olingo ima ovisnosti o EclipseLinku kao svom JPA dobavljaču i također koristi drugačiju verziju JAX-RS od Spring Boot-a.

3.1. Razredi domena

Prvi korak za implementaciju usluge OData na temelju JPA s Olingom je stvaranje entiteta naše domene. U ovom jednostavnom primjeru stvorit ćemo samo dvije klase - Proizvođač automobila i CarModel - s jednim odnosom jedan prema više:

@Entity @Table (name = "car_maker") CarMaker javne klase {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) private Long id; @NotNull privatni naziv niza; @OneToMany (mappedBy = "maker", orphanRemoval = true, cascade = CascadeType.ALL) modeli privatnog popisa; // ... izostavljeni su geteri, postavljači i hashcode} @Entity @Table (name = "car_model") javna klasa CarModel {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @NotNull privatni naziv niza; @NotNull private Integer godina; @NotNull private String sku; @ManyToOne (neobavezno = false, fetch = FetchType.LAZY) @JoinColumn (name = "maker_fk") privatni proizvođač CarMaker; // ... izostavljeni getteri, postavljači i hashcode}

3.2. ODataJPAServiceFactory Provedba

Ključna komponenta koju moramo pružiti Olingu kako bi posluživali podatke s JPA domene je konkretna implementacija apstraktne klase tzv. ODataJPAServiceFactory. Ova klasa bi se trebala proširiti ODataServiceFactory i radi kao adapter između JPA i OData. Nazvat ćemo ovu tvornicu CarsODataJPAServiceFactory, nakon glavne teme za našu domenu:

@Component javna klasa CarsODataJPAServiceFactory proširuje ODataJPAServiceFactory {// ostale su metode izostavljene ... @Prevladajte javni ODataJPAContext initializeODataJPAContext () baca ODataJPARuntimeException {ODataJPAContext ctx = getOData (getOData (getOData); ODataContext octx = ctx.getODataContext (); Zahtjev za HttpServletRequest = (HttpServletRequest) octx.getParameter (ODataContext.HTTP_SERVLET_REQUEST_OBJECT); EntityManager em = (EntityManager) zahtjev .getAttribute (EntityManagerFilter.EM_REQUEST_ATTRIBUTE); ctx.setEntityManager (em); ctx.setPersistenceUnitName ("zadani"); ctx.setContainerManaged (true); povrat ctx; }} 

Olingo zove inicijaliziranjeJPAContext () metoda ako ova klasa dobiva novu ODataJPAContext koristi se za obradu svakog zahtjeva OData. Ovdje koristimo getODataJPAContext () metodom iz osnovne klase kako bismo dobili "običnu" instancu koju zatim izvršimo neko prilagođavanje.

Ovaj je postupak pomalo zamršen, pa nacrtajmo UML sekvencu kako bismo vizualizirali kako se sve ovo događa:

Imajte na umu da namjerno koristimo setEntityManager () umjesto setEntityManagerFactory (). Mogli bismo ga dobiti od Springa, ali ako ga proslijedimo Olingu, sukobit će se s načinom na koji Spring Boot upravlja svojim životnim ciklusom - posebno kada se radi o transakcijama.

Iz tog razloga pribjeći ćemo već postojećem EntityManager instanci i obavijestite ga da se životnim ciklusom upravlja izvana. Ubrizgano EntityManager instanca dolazi iz atributa dostupnog na trenutni zahtjev. Kasnije ćemo vidjeti kako postaviti ovaj atribut.

3.3. Registracija resursa za Jersey

Sljedeći korak je registracija našeg ServiceFactory s Olingovim runtimeom i registrirajte Olingoovu ulaznu točku s JAX-RS runtimeom. Učinit ćemo to u a ResourceConfig izvedena klasa, gdje također definiramo OData put za našu uslugu / odata:

@Component @ApplicationPath ("/ odata") javna klasa JerseyConfig proširuje ResourceConfig {public JerseyConfig (CarsODataJPAServiceFactory serviceFactory, EntityManagerFactory emf) {ODataApplication app = new ODataApplication (); app .getClasses () .forEach (c -> {if (! ODataRootLocator.class.isAssignableFrom (c)) {register (c);}}); registrirajte se (novi CarsRootLocator (serviceFactory)); registracija (novi EntityManagerFilter (emf)); } // ... ... izostavljene su druge metode}

Olingo je osiguran ODataPrimjena je redovni JAX-RS Primjena klasa koja registrira nekoliko davatelja usluga koristeći standardni povratni poziv getClasses ().

Možemo koristiti sve osim ODataRootLocator razred kakav jest. Ovaj je odgovoran za instanciranje našeg ODataJPAServiceFactory implementacija pomoću Jave newInstance () metoda. No, budući da želimo da nam Proljeće upravlja, trebamo ga zamijeniti prilagođenim lokatorom.

Ovaj lokator vrlo je jednostavan JAX-RS resurs koji proširuje Olingove dionice ODataRootLocator i vraća se našim proljećem ServiceFactory kada je potrebno:

@Path ("/") javna klasa CarsRootLocator proširuje ODataRootLocator {private CarsODataJPAServiceFactory serviceFactory; javni CarsRootLocator (CarsODataJPAServiceFactory serviceFactory) {this.serviceFactory = serviceFactory; } @Override public ODataServiceFactory getServiceFactory () {return this.serviceFactory; }} 

3.4. EntityManager filtar

Posljednji preostali dio za našu OData uslugu EntityManagerFilter. Ovaj filtar ubrizgava EntityManager u trenutnom zahtjevu, tako da je dostupan ServiceFactory. To je jednostavan JAX-RS @Provider klasa koja provodi oboje ContainerRequestFilter i ContainerResponseFilter sučelja, tako da može pravilno obrađivati ​​transakcije:

@Provider javna statička klasa EntityManagerFilter implementira ContainerRequestFilter, ContainerResponseFilter {public static final String EM_REQUEST_ATTRIBUTE = EntityManagerFilter.class.getName () + "_ENTITY_MANAGER"; privatni konačni EntityManagerFactory emf; @Context private HttpServletRequest httpRequest; javni EntityManagerFilter (EntityManagerFactory emf) {this.emf = emf; } @Override javni void filtar (ContainerRequestContext ctx) baca IOException {EntityManager em = this.emf.createEntityManager (); httpRequest.setAttribute (EM_REQUEST_ATTRIBUTE, em); if (! "GET" .equalsIgnoreCase (ctx.getMethod ())) {em.getTransaction (). begin (); }} @Override javni void filter (ContainerRequestContext requestContext, ContainerResponseContext responseContext) baca IOException {EntityManager em = (EntityManager) httpRequest.getAttribute (EM_REQUEST_ATTRIBUTE); if (! "GET" .equalsIgnoreCase (requestContext.getMethod ())) {EntityTransaction t = em.getTransaction (); if (t.isActive () &&! t.getRollbackOnly ()) {t.commit (); }} em.close (); }} 

Prvi filtar() metoda, pozvana na početku zahtjeva za resursom, koristi ponuđeni EntityManagerFactory stvoriti novi EntityManager instanci, koja se zatim stavlja pod atribut kako bi je kasnije mogao oporaviti ServiceFactory. Također preskačemo GET zahtjeve jer ne bi trebali imati nuspojava, pa nam neće trebati transakcija.

Drugi filtar() metoda se poziva nakon što je Olingo završio obradu zahtjeva. Ovdje također provjeravamo i metodu zahtjeva i po potrebi izvršavamo transakciju.

3.5. Testiranje

Isprobajmo našu implementaciju pomoću jednostavnog kovrča naredbe. Prvo što možemo učiniti je dobiti usluge $ metapodaci dokument:

curl // localhost: 8080 / odata / $ metapodaci

Očekivano, dokument sadrži dvije vrste - Proizvođač automobila i CarModel - i udruga. Poigrajmo se malo više s našom uslugom dohvaćajući kolekcije i entitete najviše razine:

curl // localhost: 8080 / odata / CarMakers curl // localhost: 8080 / odata / CarModels curl // localhost: 8080 / odata / CarMakers (1) curl // localhost: 8080 / odata / CarModels (1) curl // localhost : 8080 / odata / CarModels (1) / CarMakerDetails 

Ajmo sada testirati jednostavan upit koji vraća sve Proizvođači automobila gdje mu ime počinje s "B":

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

Potpuniji popis primjera URL-ova dostupan je u našem članku Vodič za protokol OData.

5. Zaključak

U ovom smo članku vidjeli kako stvoriti jednostavnu OData uslugu potpomognutu JPA domenom pomoću Olingo V2.

Od ovog pisanja postoji otvoreno izdanje o Olingovoj JIRA koja prati radove na JPA modulu za V4, ali zadnji komentar datira iz 2016. Tu je i nezavisni JPA adapter otvorenog koda hostiran u SAP-ovom GitHub spremištu koji, iako neobjavljen, čini se da je u ovom trenutku cjelovitiji od Olingova.

Kao i obično, sav kôd za ovaj članak dostupan je na GitHubu.