Краткий обзор новых возможностей JPA-RS в EclipseLink

в 16:13, , рубрики: eclipse, java, jpa, JPARS, oracle, rest, RESTful

EclipseLink — это ORM фрэймворк с открытым исходным кодом, разрабатываемый Eclipse Foundation. В конце года запланирован выход версии 2.6.0. проекта. В преддверии этого, я хочу ознакомить вас с некоторыми новыми возможностями службы JPA-RS, которая является частью EclipseLink.
JPA-RS позволяет автоматически генерировать RESTful сервисы на базе предоставленной пользователем JPA модели. При этом практически никакой дополнительной работы от пользователя не требуется.

Версия сервиса в URL

От версии к версии возможно изменение семантики протокола передачи данных. JSON-cхема ресурсов в версии 2.0 сервиса отличается от JSON-схемы, используемой в предыдущей версии. Для обеспечения совместимости JPA-RS умеет возвращать данные используя старые форматы. Для этого версия протокола вынесена в URL.

Базовый URL выглядит так:

http(s)://{server:port}/{app}/persistence/{version}/{persistent-unit}/...

Где:

  • server:port — адрес и порт сервера.
  • app — имя вашего приложения (context root).
  • persistence — Точка входа в JPA-RS приложение. Константа.
  • version — Не обязательный. Версия JPA-RS. На данный момент поддерживаются значения «v1.0» (предыдущая версия), «v2.0» (новая версия) и «latest» (последняя версия).
  • persistent-unit — Имя persistent unit как указано в persistence.xml.

Версия сервиса может быть пропущена, в этом случае используется значение «v1.0».

Примеры:

Запрос объекта Car с примарным ключом 1 из персистент юнита car-pu используя семантику протокола версии 1.0:

http(s)://localhost:8080/jpars-test/persistence/car-pu/entity/Car/1

Тоже самое:

http(s)://localhost:8080/jpars-test/persistence/v1.0/car-pu/entity/Car/1

Запрос тех же данных, но используя семантику сервиса версии 2.0:

http(s)://localhost:8080/jpars-test/persistence/v2.0/car-pu/entity/Car/1

Так как последняя версия на данный момент 2.0, то следующий запрос вернет тоже самое, что и предыдущий:

http(s)://localhost:8080/jpars-test/persistence/latest/car-pu/entity/Car/1

Далее, исключительно для читабельности, я обозначу http(s)://localhost:8080/jpars-test/persistence/v2.0 как {root}.

Постраничный вывод

JPARS позволяет разделять на страницы длинные списки и возвращать только выбранную пользователем страницу. Это работает для запросов (Named Query) и для полей типа Collection.
Постраничный вывод — это единственная функция JPA-RS, требующая конфигурации. Это делается с помощью аннотаций.

Рассмотрим следующий пример:

@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;
	    
	// Getters and setters are skipped	
}

Это стандартный entity класс c двумя JPARS аннотациями.

@RestPageableQueries({
        @RestPageableQuery(queryName = "Car.findAllPageable", limit = 20)
})

Этот кусок кода говорит, что запрос Car.findAllPageable должен выдаваться через RESTful сервис постранично с размером страницы — 20 записей.
Для конфигурации постраничного вывода при вызове используются два параметра:

  • limit — размер страницы. Не может быть выше значения limit указанного в аннотации @RestPageableQuery. Оно же является значением по умолчанию.
  • offset — смещение от начала списка. Порядковый номер первой возвращаемой записи. Значение по умолчению — 0.

К примеру, следующие запросы аналогичны:

GET <root>/query/Car.findAllPageable
GET <root>/query/Car.findAllPageable?limit=20&offset=0

и вернут следующий JSON:

{
    "items": [
        {
            "id": 1,
            "name": "Mazda MX-5",
            ...
        },
	... <еще 19 записей> ...
    ],
    "hasMore": true,
    "limit": 20,
    "offset": 0,
    "count": 20,
    "links": [
        {
            "rel": "self",
            "href": "{root}/query/Car.findAllPageable"
        }
        {
            "rel": "canonical",
            "href": "{root}/query/Car.findAllPageable"
        }
        {
            "rel": "next",
            "href": "{root}/query/Car.findAllPageable?offset=20"
        }
    ]
}

Ответ сервера также содержит дополнительную информацию:

  • hasMore — true если данная страница не является последней.
  • limit — размер страницы используемый сервером.
  • offset — используемое смещение.
  • count — количество записей на странице.
  • ссылка next — URL следующей страницы (если доступна)
  • ссылка prev — URL предыдущей страницы (если доступна)

Вторая страница:

{root}/query/Car.findAllPageable?offset=20

или

{root]/query/Car.findAllPageable?limit=20&offset=20

Третья страница размером 10 записей:

{root}/query/Car.findAllPageable?limit=10&offset=20

7 записей начиная с третей:

{root}/query/Car.findAllPageable?limit=7&offset=3

Сервер всегда использует минимальный limit из укзанного в запросе и используемного в аннотиции. То есть

{root}/query/Car.findAllPageable?limit=100

вернет только 20 записей. При этом значение limit в ответе сервера будет 20, как в примере выше.

Фильтрация полей

При запросе данных иногда бывает нужно вернуть не всю запись, а только некоторые поля. К примеру, поле longDescr класса Car содержит текст внушительного размера, который мы не хотим передавать по сети. Именно для этого служит фильтрация полей.

Фильтрация конфигурируется двумя параметрами:

  • fields — список полей, возвращаемых сервером. Поля разделяются запятыми.
  • excludeFields — список полей, которые не возвращаются сервером.

К примеру:

GET {root}/entity/Car/1?fields=id,name,shortDescr

Вернет только поля ud, name и shortDescr класса Car:

{
    "id": 1,
    "name": "Mazda MX-5",
    "shortDescr": "двухместный родстер",
    ...
}

Тоже самое вернет и следующий запрос:

GET {root}/entity/Car/1?excludeFields=longDescr

При попытке использовать оба параметра fields и excludeFields в одном запросе, сервер вернет ошибку.

Метаданные

Метаданные содержат дополнительную информацию о ресурсе. В нашем слусае это ссылка на JSON схему ресурса и базовый URL.

К примеру, метаданные для нашего класса Car выглядят так:

{
    "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"
        }
    ]
}

Получить метаданные для класса Car можно вот так:

{root}/metadata-catalog/entity/Car

Для запроса Car.findAll так:

{root}/metadata-catalog/query/Car.findAll

Еще один способ получения метаданных — вызов OPTIONS метода на базовый URL ресурса.

OPTIONS <root>/entity/Basket

Сервер вернет ссылку на метаданные в заголовке Link c rel=«describedby»

Link: <root/metadata-catalog/entity/Basket>; rel="describedby"

Каталог ресурсов

Каталог ресурсов, как оглавление книги, содержит метаданные для всех доступтых ресурсов. Это правильное место начала работы с незнакомой службой. Здесь всегда можно посмотреть доступные объкты и запросы.

Каталог ресурсов доступен по следующему адресу:

GET <root>/metadata-catalog

Ответ сервера:

{
    "items": [
        {
            "name": "Car",
            ...
        },
        {
            "name": "Car.findAll",
            ...
        },
        {
            "name": "Car.findAllPageable",
            ...
        }
    ],
    "links": [
        {
            "rel": "canonical",
            "href": "<root>/metadata-catalog"
        }
    ]
}

Я урезал ответ сервера исключительно для читабельности. Медаданные для всех ресурсов выглядят также как указано в начале главы.

JSON-схема ресурсов

Хотя JSON схема пока не является утвержденным стандартом и работы над спецификацией еще не закончены, JPS-RS уже частично поддерживает draft 4 спецификации. Это касается формата вывода объектов и списка объектов.

URL метаданных ресурса можно использовать и для получения его (ресурса) JSON схемы. Для этого выполняем HTTP GET запрос используя тип медиа «application/schema+json». То есть установив в HTTP заголовке «accept» значение «application/schema+json».

Получение схемы для класса Car:

GET <root>/metadata-catalog/entity/Car HTTP/1.1
Accept-Encoding: gzip,deflate
accept: application/schema+json
Host: <host:port>
Proxy-Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

Ответ сервера:

{
    "$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"
        },
    },
    "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"
        }
    ]
}

На этом все. Надеюсь, вам понравился обзор.
 
Nightly builds EclipseLink можно скачать здесь:
http://www.eclipse.org/eclipselink/downloads/nightly.php

Автор: m0mus

Источник

Поделиться

* - обязательные к заполнению поля