Pengenalan OData dengan Olingo

1. Pengenalan

Tutorial ini adalah tindak lanjut dari Panduan Protokol OData kami, di mana kami telah meneroka asas-asas protokol OData.

Sekarang, kita akan melihat bagaimana melaksanakan perkhidmatan OData sederhana menggunakan perpustakaan Apache Olingo .

Perpustakaan ini menyediakan kerangka kerja untuk mengekspos data menggunakan protokol OData, sehingga memungkinkan akses berdasarkan maklumat yang mudah dan standard berdasarkan yang akan terkunci dalam pangkalan data dalaman.

2. Apakah Olingo?

Olingo adalah salah satu implementasi OData "unggulan" yang tersedia untuk lingkungan Java - yang lainnya adalah Kerangka SDD OData. Ia dikendalikan oleh Apache Foundation dan terdiri daripada tiga modul utama:

  • Java V2 - perpustakaan pelanggan dan pelayan yang menyokong OData V2
  • Java V4 - perpustakaan pelayan yang menyokong OData V4
  • Javascript V4 - Javascript, pustaka khusus pelanggan yang menyokong OData V4

Dalam artikel ini, kami akan membahas hanya perpustakaan Java V2 sisi pelayan, yang menyokong integrasi langsung dengan JPA . Perkhidmatan yang dihasilkan menyokong operasi CRUD dan ciri protokol OData lain, termasuk pesanan, paging dan penapisan.

Olingo V4, sebaliknya, hanya menangani aspek tahap rendah dari protokol, seperti perundingan jenis kandungan dan penghuraian URL. Ini bermaksud kita, pemaju, akan membuat kod semua butiran terperinci mengenai perkara seperti penghasilan metadata, menghasilkan pertanyaan akhir berdasarkan parameter URL, dll.

Mengenai perpustakaan klien JavaScript, kami meninggalkannya untuk saat ini kerana, kerana OData adalah protokol berasaskan HTTP, kami dapat menggunakan perpustakaan REST untuk mengaksesnya.

3. Perkhidmatan Olingo Java V2

Mari buat perkhidmatan OData ringkas dengan dua EntitySet yang telah kami gunakan dalam pengenalan ringkas protokol itu sendiri. Pada intinya, Olingo V2 hanyalah sekumpulan sumber daya JAX-RS dan, dengan itu, kita perlu menyediakan infrastruktur yang diperlukan untuk menggunakannya. Yaitu, kita memerlukan pelaksanaan JAX-RS dan wadah servlet yang serasi.

Untuk contoh ini, kami memilih untuk menggunakan Spring Boot - kerana ia menyediakan cara cepat untuk mewujudkan persekitaran yang sesuai untuk menjadi tuan rumah perkhidmatan kami. Kami juga akan menggunakan penyesuai JPA Olingo, yang "bercakap" secara langsung kepada EntityManager yang dibekalkan pengguna untuk mengumpulkan semua data yang diperlukan untuk membuat EntityDataModel OData .

Walaupun bukan syarat yang ketat, termasuk penyesuai JPA sangat memudahkan tugas mewujudkan perkhidmatan kami.

Selain pergantungan Spring Boot standard, kita perlu menambah beberapa balang Olingo:

 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   

Versi terbaru dari perpustakaan tersebut boleh didapati di repositori Maven's Central:

  • olingo-odata2-teras
  • olingo-odata2-jpa-pemproses-teras
  • olingo-odata2-jpa-pemproses-ref

Kami memerlukan pengecualian tersebut dalam senarai ini kerana Olingo mempunyai kebergantungan pada EclipseLink sebagai penyedia JPAnya dan juga menggunakan versi JAX-RS yang berbeza daripada Spring Boot.

3.1. Kelas Domain

Langkah pertama untuk melaksanakan perkhidmatan OData berasaskan JPA dengan Olingo adalah membuat entiti domain kami. Dalam contoh mudah ini, kami akan membuat hanya dua kelas - CarMaker dan CarModel - dengan satu hubungan satu-ke-banyak:

@Entity @Table(name="car_maker") public class CarMaker { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; @NotNull private String name; @OneToMany(mappedBy="maker",orphanRemoval = true,cascade=CascadeType.ALL) private List models; // ... getters, setters and hashcode omitted } @Entity @Table(name="car_model") public class CarModel { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @NotNull private String name; @NotNull private Integer year; @NotNull private String sku; @ManyToOne(optional=false,fetch=FetchType.LAZY) @JoinColumn(name="maker_fk") private CarMaker maker; // ... getters, setters and hashcode omitted }

3.2. Pelaksanaan ODataJPAServiceFactory

Komponen utama yang perlu kita berikan kepada Olingo untuk melayani data dari domain JPA adalah pelaksanaan konkrit dari kelas abstrak yang disebut ODataJPAServiceFactory. Kelas ini harus memperluas ODataServiceFactory dan berfungsi sebagai penyesuai antara JPA dan OData. Kami akan menamakan kilang ini CarsODataJPAServiceFactory , selepas topik utama untuk domain kami:

@Component public class CarsODataJPAServiceFactory extends ODataJPAServiceFactory { // other methods omitted... @Override public ODataJPAContext initializeODataJPAContext() throws ODataJPARuntimeException { ODataJPAContext ctx = getODataJPAContext(); ODataContext octx = ctx.getODataContext(); HttpServletRequest request = (HttpServletRequest) octx.getParameter( ODataContext.HTTP_SERVLET_REQUEST_OBJECT); EntityManager em = (EntityManager) request .getAttribute(EntityManagerFilter.EM_REQUEST_ATTRIBUTE); ctx.setEntityManager(em); ctx.setPersistenceUnitName("default"); ctx.setContainerManaged(true); return ctx; } } 

Olingo memanggil kaedah initializeJPAContext () jika kelas ini mendapatkan ODataJPAContext baru yang digunakan untuk menangani setiap permintaan OData. Di sini, kami menggunakan kaedah getODataJPAContext () dari classe dasar untuk mendapatkan contoh "biasa" yang kemudian kami lakukan beberapa penyesuaian.

Proses ini agak rumit, jadi mari kita lukis urutan UML untuk menggambarkan bagaimana semua ini berlaku:

Perhatikan bahawa kami sengaja menggunakan setEntityManager () dan bukannya setEntityManagerFactory (). Kami dapat satu dari Spring tetapi, jika kami menyebarkannya ke Olingo, ia akan bertentangan dengan cara Spring Boot mengendalikan kitaran hayatnya - terutama ketika berurusan dengan transaksi.

Atas sebab ini, kami akan menggunakan contoh EntityManager yang sudah ada dan memaklumkan bahawa kitaran hayatnya dikendalikan secara luaran. Instance EntityManager yang disuntik berasal dari atribut yang tersedia atas permintaan semasa. Kami kemudian akan melihat cara menetapkan atribut ini.

3.3. Pendaftaran Sumber Jersey

Langkah seterusnya adalah untuk cuba untuk kami ServiceFactory dengan runtime Olingo dan mendaftar pintu masuk Olingo dengan runtime JAX-RS. Kami akan melakukannya di dalam kelas turunan ResourceConfig , di mana kami juga menentukan jalan OData agar perkhidmatan kami menjadi / odata :

@Component @ApplicationPath("/odata") public class JerseyConfig extends ResourceConfig { public JerseyConfig(CarsODataJPAServiceFactory serviceFactory, EntityManagerFactory emf) { ODataApplication app = new ODataApplication(); app .getClasses() .forEach( c -> { if ( !ODataRootLocator.class.isAssignableFrom(c)) { register(c); } }); register(new CarsRootLocator(serviceFactory)); register(new EntityManagerFilter(emf)); } // ... other methods omitted }

Olingo dengan syarat ODataApplication ialah JAX-RS biasa Permohonan kelas itu mendaftarkan pembekal beberapa menggunakan callback standard getClasses () .

Kita boleh menggunakan semua kecuali kelas ODataRootLocator sebagaimana adanya . Yang khusus ini bertanggungjawab untuk menunjukkan pelaksanaan ODataJPAServiceFactory kami menggunakan kaedah JavaInstance () baru . Tetapi, kerana kami mahu Spring menguruskannya untuk kami, kami perlu menggantinya dengan lokasi khas

Pencari ini adalah sumber JAX-RS yang sangat mudah yang memanjangkan Olingo ini saham ODataRootLocator dan ia mengembalikan Spring diurus kami ServiceFactory apabila diperlukan:

@Path("/") public class CarsRootLocator extends ODataRootLocator { private CarsODataJPAServiceFactory serviceFactory; public CarsRootLocator(CarsODataJPAServiceFactory serviceFactory) { this.serviceFactory = serviceFactory; } @Override public ODataServiceFactory getServiceFactory() { return this.serviceFactory; } } 

3.4. Penapis EntityManager

The last remaining piece for our OData service the EntityManagerFilter. This filter injects an EntityManager in the current request, so it is available to the ServiceFactory. It's a simple JAX-RS @Provider class that implements both ContainerRequestFilter and ContainerResponseFilter interfaces, so it can properly handle transactions:

@Provider public static class EntityManagerFilter implements ContainerRequestFilter, ContainerResponseFilter { public static final String EM_REQUEST_ATTRIBUTE = EntityManagerFilter.class.getName() + "_ENTITY_MANAGER"; private final EntityManagerFactory emf; @Context private HttpServletRequest httpRequest; public EntityManagerFilter(EntityManagerFactory emf) { this.emf = emf; } @Override public void filter(ContainerRequestContext ctx) throws 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) throws 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(); } } 

The first filter() method, called at the start of a resource request, uses the provided EntityManagerFactory to create a new EntityManager instance, which is then put under an attribute so it can later be recovered by the ServiceFactory. We also skip GET requests since should not have any side effects, and so we won't need a transaction.

The second filter() method is called after Olingo has finished processing the request. Here we also check the request method, too, and commit the transaction if required.

3.5. Testing

Let's test our implementation using simple curl commands. The first this we can do is get the services $metadata document:

curl //localhost:8080/odata/$metadata

As expected, the document contains two types – CarMaker and CarModel – and an association. Now, let's play a bit more with our service, retrieving top-level collections and entities:

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 

Now, let's test a simple query returning all CarMakers where its name starts with ‘B':

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

A more complete list of example URLs is available at our OData Protocol Guide article.

5. Conclusion

In this article, we've seen how to create a simple OData service backed by a JPA domain using Olingo V2.

Pada penulisan ini, terdapat isu terbuka mengenai JIRA Olingo yang mengesan karya pada modul JPA untuk V4, tetapi komen terakhir bermula pada tahun 2016. Terdapat juga penyesuai JPA sumber terbuka pihak ketiga yang dihoskan di repositori GitHub SAP yang, walaupun belum dirilis, nampaknya lebih lengkap pada masa ini daripada Olingo.

Seperti biasa, semua kod untuk artikel ini terdapat di GitHub.