At the end of this year planned a release of EclipseLink 2.6.0. EclipseLink is an open source ORM framework developed by Eclipse Foundation. Here I would like to briefly introduce some new features of JPA-RS. JPA-RS is a part of EclipseLink. It’s a service allowing automatic generation of RESTful web service based on provided JPA entity model.
1. Protocol semantics version
Protocol semantics may change from version to version. A JSON schema of resources in JPA-RS 2.0 differs from the version 1.0 of the service. To make the service backwards compatible we added a version number to the URL.
The URL template looks like this:
http(s)://{server:port}/{app}/persistence/{version}/{persistent-unit}/...
Where:
- server:port – server IP address (DNS name) + port\
- app – your application name.
- persistence – JPARS application name. Constant.
- version – JPARS version. Currently only values “v1.0”, “v2.0” and “latest” are supported.
- persistent-unit – Your persistent unit name. Defined in persistece.xml file.
To make the URL format compatible with the previous version of the service it is possible to omit version parameter from the URL. In this case value “v1.0” is used by the server.
It is possible to use latest keyword instead of the version number. In this case the latest protocol semantic version will be used. It is “v2.0” at the moment.
Examples:
This request gets a Car entity with id = 1 using car-pu persistent unit from the localhost. It returns data in JPARS 1.0 format.
http(s)://localhost:8080/app/persistence/car-pu/entity/Car/1
it’s the same as:
http(s)://localhost:8080/app/persistence/v1.0/car-pu/entity/Car/1
This request does the same but uses JPARS 2.0 protocol semantic:
http://localhost:8080/app/persistence/v2.0/car-pu/entity/Car/1
This request does absolutely the same as the previous one:
http://localhost:8080/app/persistence/latest/car-pu/entity/Basket/1
Later in the article I will reference this URL http://localhost:8080/app/persistence/latest/car-pu as {root}.
2. Pagination
Pagination is not available for all collection resources by default. It’s configured on the server side using annotations. If a request with pagination parameters comes to the resource which doesn’t support it exception is thrown.
Resources supporting pagination:
- Named query resources: {root}/query/{query-name}
- Single attributes of collection type {root}/entity/{entity-name}/{primary-key}/{attribute-name}
The server side configuration is done by annotating your entity with special paging annotations:
@RestPageableQueries
This is a container for a list of @RestPageableQuery annotations. It’s placed on entity class. It works the similar way as @NamedQueries annotation.
@RestPageableQuery(queryName=, limit=)
This annotation must be placed inside @RestPageableQueries annotation. It says that named query with name *named_query_name* is pageable and the default number of items per page is *limit*. If limit is not defined the default value of 100 used. Named query with *named_query_name* must exist.
Sample:
@Entity @Table(name = "CAR") @NamedQueries({ @NamedQuery( name = "Car.findAll", query = "SELECT c FROM Car c ORDER BY c.name"), @NamedQuery( name = "Car.findAllPageable", query = "SELECT c FROM Car c ORDER BY c.name"), }) @RestPageableQueries({ @RestPageableQuery(queryName = "Car.findAllPageable", limit = 20) }) public class Car { @Id @Column(name = "CAR_ID") private Integer id; @Column(name = "CAR_NAME") private String name; @Column(name = "CAR_SHORT_DESCR") private String shortDescr; @Column(name = "CAR_LONG_DESCR") private String longDescr; @ManyToOne @JoinColumn(name = "CAR_ID") private Owner owner; // Getters and setters are skipped } @Entity @Table(name = "OWNER") public class Owner { @Id @Column(name = "OWNER_ID") private Integer id; @Column(name = "OWNER_NAME") private String name; @OneToMany(mappedBy = "owner") @RestPageable(limit = 20) List cars; // Getters and setters are skipped }
@RestPageable(limit=items_per_page)
This annotation defines a pageable collection attribute. It must be placed on the collection field. The number of items per page is defined by the optional limit parameter. Default limit value is 100.
Sample:
@OneToMany(mappedBy = "owner") @RestPageable(limit = 20) List cars;
If pagination is supported for a resource the following two optional request parameters can be used to control it.
- limit: sets the page size and controls how many rows to return. If this parameter is not used then the default value is used. The default value is set in the corresponding @RestPageableQuery or @RestPageable annotation.
- offset: sets the start record of the returning data from the beginning of the list. If not present the default value of 0 is used.
For example these requests return the first page (20 records) of Cars.findAllPageable query:
{root}/query/Car.findAllPageable {root}/query/Car.findAllPageable?limit=20
This is the second page:
{root}/query/Car.findAllPageable?offset=20 {root}/query/Car.findAllPageable?limit=20&offset=20
Third page with 10 records per page:
{root}/query/Car.findAllPageable?limit=10&offset=20
7 records starting from the third one:
{root}/query/Car.findAllPageable?limit=7&offset=3
Server calculates the actual limit as minimum from limit query parameter and default limit specified in the annotation.
For example, the limit=20 will be used for the following query:
{root}/query/Car.findAllPageable?limit=100
Lets take a look at the response of {root}/query/Car.findAllPageable?offset=20 query:
{ "items": [ { "id": 21, "name": "Mazda MX-5", ... }, ... <19 more records> ... ], "hasMore": true, "limit": 20, "offset": 20, "count": 20, "links": [ { "rel": "self", "href": "/query/Car.findAllPageable?offset=20" }, { "rel": "canonical", "href": "/query/Car.findAllPageable?offset=20" }, { "rel": "next", "href": "/query/Car.findAllPageable?offset=40" }, { "rel": "prev", "href": "/query/Car.findAllPageable?offset=0" } ] }
The response contains some additional properties and links:
- ‘next’ link – link to the next page if available.
- ‘prev’ link – link to the previous page if available.
- limit: the actual paging size used by the server.
- offset: the actual index from which the records are returned.
- count: the total number of records in the current response.
- hasMore: this is a Boolean property to indicate whether there are more pages that satisfy the request. Always returned.
3. Fields Filtering
This feature allows user to select fields which should be returned. It’s done with help of two query parameters fields and excludeFields:
- fields parameter defines a list of returned fields.
- excludeFields parameter defines a list of excluded fields.
NOTE
fields and excludeFields parameters are mutual exclusive. If both of them are present in the request the error is returned.
For example:
GET {root}/entity/Car/1?fields=id,name,shortDescr
Returns only fields *id*, *name* and *shortDescr* of class Car:
{ "id": 1, "name": "Mazda MX-5", "shortDescr": "lightweight two-seater roadster", ... }
This request returns the same as above:
GET /entity/Car/1?excludeFields=longDescr,owner
4. Resources metadata
Resource metadata is information about the resource that is not specific to a particular representation. It contains a link to a resource schema, canonical link to resource and other data.
Metadata for entity resource is provided at:
{root}/metadata-catalog/entity/{entityName}
For example, metadata for Car entity is provided at:
{root}/metadata-catalog/entity/Car
Metadata for query resource is provided at:
{root}/metadata-catalog/query/queryName
Metadata for Car.findAll query is provided at:
{root}/query/Car.findAll
Another way of getting resource metadata is using OPTIONS method against the resource URL.
For example, metadata for Car entity can be retrieved this way:
OPTIONS {root}/entity/Basket
The response header contains a link to the resource metadata URL with “describedby” rel.
Link: <{root}/metadata-catalog/entity/Car>; rel="describedby"
There are two media types that can be used to describe a resource: application/json and application/schema+json. The default value is the former, which returns a very simple description of the resource, including the resource name, a link to the resource instance, and a link to the JSON schema of the resource.
For example if application/json is used
GET {root}/metadata-catalog/entity/Car
returns
{ "name": "Car", "links": [ { "rel": "alternate", "href": "{root}/metadata-catalog/entity/Car", "mediaType": "application/schema+json" }, { "rel": "canonical", "href": "{root}/metadata-catalog/entity/Car", "mediaType": "application/json" }, { "rel": "describes", "href": "{root}/entity/Car" } ] }
If application/schema+json media type is used (header ‘accept’ value ‘application/schema+json’) to access this resource metadata URI, then a JSON schema for the resource is returned.
{ "$schema": "{root}/metadata-catalog/entity/Car#", "allOf": [ { "$ref": "rest-schemas/#/singularResource" } ], "title": "Car", "properties": { "id": { "type": "number" }, "name": { "type": "string" }, "shortDescr": { "type": "string" }, "longDescr": { "type": "string" }, "owner": { "$ref": "{root}/entity/Owner#" } }, "links": [ { "rel": "describedby", "href": "{root}/entity/Car" }, { "rel": "find", "href": "{root}/entity/Car/{primaryKey}", "method": "GET" }, { "rel": "create", "href": "{root}/entity/Car", "method": "PUT" }, { "rel": "update", "href": "{root}/entity/Car", "method": "POST" }, { "rel": "delete", "href": "{root}/entity/Car/{primaryKey}", "method": "DELETE" } ] }
5. Metadata catalog
Metadata catalog is a catalog of all available entities and queries metadata. Metadata catalog is accesible at:
GET {root}/metadata-catalog
For example, a metadata catalog for car-pu persistent unit is provided here:
http://localhost:7001/app/persistence/v2.0/car-pu/metadata-catalog
Response:
{ "items": [ { "name": "Car", ... }, { "name": "Owner", ... }, { "name": "Car.findAll", ... }, { "name": "Car.findAllPageable", ... } ], "links": [ { "rel": "canonical", "href": "{root}/metadata-catalog" } ] }
That is it. I hope you liked the article.
If you want to try JPA-RS 2.0, EclipseLink nightly builds can be downloaded here.