Inleiding tot OData met Olingo

1. Inleiding

Deze tutorial is een vervolg op onze OData Protocol Guide, waarin we de basisprincipes van het OData-protocol hebben onderzocht.

Nu zullen we zien hoe we een eenvoudige OData-service kunnen implementeren met behulp van de Apache Olingo-bibliotheek.

Deze bibliotheek biedt een raamwerk om gegevens vrij te geven met behulp van het OData-protocol, waardoor gemakkelijke, op standaarden gebaseerde toegang tot informatie mogelijk is die anders zou worden opgesloten in interne databases.

2. Wat is Olingo?

Olingo is een van de "aanbevolen" OData-implementaties die beschikbaar zijn voor de Java-omgeving - de andere is het SDL OData Framework. Het wordt onderhouden door de Apache Foundation en bestaat uit drie hoofdmodules:

  • Java V2 - client- en serverbibliotheken die OData V2 ondersteunen
  • Java V4 - serverbibliotheken die OData V4 ondersteunen
  • Javascript V4 - Javascript, client-only bibliotheek die OData V4 ondersteunt

In dit artikel behandelen we alleen de server-side V2 Java-bibliotheken, die directe integratie met JPA ondersteunen. De resulterende service ondersteunt CRUD-bewerkingen en andere OData-protocolfuncties, waaronder ordenen, paging en filteren.

Olingo V4 daarentegen behandelt alleen de lagere aspecten van het protocol, zoals onderhandeling van het inhoudstype en het parseren van URL's. Dit betekent dat het aan ons, ontwikkelaars, is om alle essentiële details te coderen met betrekking tot zaken als het genereren van metadata, het genereren van back-endquery's op basis van URL-parameters, enz.

Wat betreft de JavaScript-clientbibliotheek, we laten deze voorlopig weg, omdat OData een op HTTP gebaseerd protocol is, we elke REST-bibliotheek kunnen gebruiken om er toegang toe te krijgen.

3. Een Olingo Java V2-service

Laten we met de twee een eenvoudige OData-service maken EntitySets die we hebben gebruikt in onze korte inleiding tot het protocol zelf. In wezen is Olingo V2 gewoon een set JAX-RS-bronnen en als zodanig moeten we de vereiste infrastructuur bieden om deze te kunnen gebruiken. We hebben namelijk een JAX-RS-implementatie en een compatibele servletcontainer nodig.

Voor dit voorbeeld we hebben ervoor gekozen om Spring Boot te gebruiken - omdat het een snelle manier biedt om een ​​geschikte omgeving te creëren om onze service te hosten. We zullen ook de JPA-adapter van Olingo gebruiken, die rechtstreeks met een door de gebruiker geleverde "praat" EntityManager om alle gegevens te verzamelen die nodig zijn om de OData's te maken EntityDataModel.

Hoewel het geen strikte vereiste is, vereenvoudigt het opnemen van de JPA-adapter de taak om onze service te creëren aanzienlijk.

Naast de standaard Spring Boot-afhankelijkheden, moeten we een paar potten van Olingo toevoegen:

 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 

De nieuwste versie van die bibliotheken is beschikbaar in de centrale repository van Maven:

  • olingo-odata2-core
  • olingo-odata2-jpa-processor-core
  • olingo-odata2-jpa-processor-ref

We hebben die uitsluitingen in deze lijst nodig omdat Olingo afhankelijk is van EclipseLink als zijn JPA-provider en ook een andere JAX-RS-versie gebruikt dan Spring Boot.

3.1. Domein klassen

De eerste stap om een ​​op JPA gebaseerde OData-service met Olingo te implementeren, is het creëren van onze domeinentiteiten. In dit eenvoudige voorbeeld maken we slechts twee klassen - Automonteur en Auto model - met een enkele een-op-veel-relatie:

@Entity @Table (name = "car_maker") openbare klasse CarMaker {@Id @GeneratedValue (strategie = GenerationType.IDENTITY) privé Lange id; @NotNull private String naam; @OneToMany (mappedBy = "maker", orphanRemoval = true, cascade = CascadeType.ALL) privélijstmodellen; // ... getters, setters en hashcode weggelaten} @Entity @Table (naam = "auto_model") openbare klasse CarModel {@Id @GeneratedValue (strategie = GenerationType.AUTO) privé Lange id; @NotNull private String naam; @NotNull privé Geheel getal jaar; @NotNull privé String sku; @ManyToOne (optioneel = false, fetch = FetchType.LAZY) @JoinColumn (naam = "maker_fk") privé-automaker; // ... getters, setters en hashcode weggelaten}

3.2. ODataJPAServiceFactory Implementatie

Het belangrijkste onderdeel dat we aan Olingo moeten leveren om gegevens uit een JPA-domein te kunnen leveren, is een concrete implementatie van een abstracte klasse met de naam ODataJPAServiceFactory. Deze klasse moet worden uitgebreid ODataServiceFactory en werkt als een adapter tussen JPA en OData. We noemen deze fabriek CarsODataJPAServiceFactory, na het hoofdonderwerp voor ons domein:

@Component openbare klasse CarsODataJPAServiceFactory breidt ODataJPAServiceFactory uit {// andere weggelaten methoden ... @Override openbare ODataJPAContext initializeODataJPAContext () gooit ODataJPARuntimeException {ODataJPAContext ctx = getODataJPAContext (); ODataContext octx = ctx.getODataContext (); HttpServletRequest-verzoek = (HttpServletRequest) octx.getParameter (ODataContext.HTTP_SERVLET_REQUEST_OBJECT); EntityManager em = (EntityManager) verzoek .getAttribute (EntityManagerFilter.EM_REQUEST_ATTRIBUTE); ctx.setEntityManager (em); ctx.setPersistenceUnitName ("standaard"); ctx.setContainerManaged (true); retourneer ctx; }} 

Olingo noemt het initializeJPAContext () methode als deze klasse een nieuw ODataJPAContext gebruikt om elk OData-verzoek af te handelen. Hier gebruiken we de getODataJPAContext () methode uit de basisklasse om een ​​"gewone" instantie te krijgen die we vervolgens aanpassen.

Dit proces is enigszins ingewikkeld, dus laten we een UML-reeks tekenen om te visualiseren hoe dit allemaal gebeurt:

Merk op dat we opzettelijk gebruiken setEntityManager () in plaats van setEntityManagerFactory (). We zouden er een kunnen krijgen van Spring, maar als we het doorgeven aan Olingo, zal het in strijd zijn met de manier waarop Spring Boot omgaat met zijn levenscyclus, vooral als het gaat om transacties.

Om deze reden zullen we onze toevlucht nemen tot het doorgeven van een reeds bestaand EntityManager instantie en laat het weten dat zijn levenscyclus extern wordt beheerd. De geïnjecteerd EntityManager instantie komt van een attribuut dat beschikbaar is op het huidige verzoek. We zullen later zien hoe u dit kenmerk kunt instellen.

3.3. Jersey Resource-registratie

De volgende stap is om ons te registreren ServiceFactory met de runtime van Olingo en registreer het toegangspunt van Olingo met de JAX-RS-runtime. We doen het in een ResourceConfig afgeleide klasse, waar we ook het OData-pad voor onze service definiëren / odata:

@Component @ApplicationPath ("/ odata") openbare klasse JerseyConfig breidt ResourceConfig uit {openbare JerseyConfig (CarsODataJPAServiceFactory serviceFactory, EntityManagerFactory emf) {ODataApplication app = nieuwe ODataApplication (); app .getClasses () .forEach (c -> {if (! ODataRootLocator.class.isAssignableFrom (c)) {register (c);}}); register (nieuwe CarsRootLocator (serviceFactory)); register (nieuwe EntityManagerFilter (emf)); } // ... andere methoden weggelaten}

Olingo heeft gezorgd ODataApplication is een gewone JAX-RS Toepassing class die een aantal providers registreert met behulp van de standaard callback getClasses ().

We kunnen alles gebruiken, behalve de ODataRootLocator klasse zoals ze is. Deze specifieke is verantwoordelijk voor het instantiëren van onze ODataJPAServiceFactory implementatie met behulp van Java's newInstance () methode. Maar aangezien we willen dat Spring het voor ons beheert, moeten we het vervangen door een aangepaste locator.

Deze locator is een zeer eenvoudige JAX-RS-bron die de voorraad van Olingo uitbreidt ODataRootLocator en het geeft onze Spring-managed terug ServiceFactory wanneer nodig:

@Path ("/") openbare klasse CarsRootLocator breidt ODataRootLocator {privé CarsODataJPAServiceFactory serviceFactory; openbare CarsRootLocator (CarsODataJPAServiceFactory serviceFactory) {this.serviceFactory = serviceFactory; } @Override openbare ODataServiceFactory getServiceFactory () {retourneer this.serviceFactory; }} 

3.4. EntityManager Filter

Het laatste overgebleven stuk voor onze OData-service is het EntityManagerFilter. Dit filter injecteert een EntityManager in het huidige verzoek, dus het is beschikbaar voor de ServiceFactory. Het is een simpele JAX-RS @Provider klasse die beide implementeert ContainerRequestFilter en ContainerResponseFilter interfaces, zodat het transacties correct kan afhandelen:

@Provider openbare statische klasse EntityManagerFilter implementeert ContainerRequestFilter, ContainerResponseFilter {openbare statische laatste String EM_REQUEST_ATTRIBUTE = EntityManagerFilter.class.getName () + "_ENTITY_MANAGER"; private finale EntityManagerFactory emf; @Context privé HttpServletRequest httpRequest; openbare EntityManagerFilter (EntityManagerFactory emf) {this.emf = emf; } @Override public void filter (ContainerRequestContext ctx) gooit 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) gooit 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 (); }} 

De eerste filter() methode, aangeroepen aan het begin van een resourceverzoek, maakt gebruik van het opgegeven EntityManagerFactory om een ​​nieuw EntityManager instantie, die vervolgens onder een attribuut wordt geplaatst, zodat het later kan worden hersteld door de ServiceFactory. We slaan ook GET-verzoeken over omdat dit geen bijwerkingen zou moeten hebben, en dus hebben we geen transactie nodig.

De seconde filter() methode wordt aangeroepen nadat Olingo klaar is met het verwerken van het verzoek. Hier controleren we ook de aanvraagmethode en leggen we de transactie vast indien nodig.

3.5. Testen

Laten we onze implementatie testen met simple krullen commando's. Het eerste dat we kunnen doen, is de services krijgen $ metadata document:

curl // localhost: 8080 / odata / $ metadata

Zoals verwacht bevat het document twee typen - Automonteur en Auto model - en een vereniging. Laten we nu wat meer spelen met onze service, verzamelingen en entiteiten op het hoogste niveau ophalen:

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 

Laten we nu een eenvoudige query testen die alles retourneert Automakers waar de naam begint met 'B':

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

Een meer complete lijst met voorbeeld-URL's is beschikbaar in ons OData Protocol Guide-artikel.

5. Conclusie

In dit artikel hebben we gezien hoe we met Olingo V2 een eenvoudige OData-service kunnen maken die wordt ondersteund door een JPA-domein.

Op het moment van schrijven is er een open probleem met Olingo's JIRA die de werken aan een JPA-module voor V4 volgt, maar de laatste opmerking dateert uit 2016. Er is ook een open-source JPA-adapter van derden die wordt gehost in de GitHub-repository van SAP, die, hoewel niet uitgebracht, lijkt het op dit moment meer functies te bevatten dan die van Olingo.

Zoals gewoonlijk is alle code voor dit artikel beschikbaar op GitHub.


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