Загрузка сущностей

Entities API позволяет различными способами загружать сущности через API:

Загрузка сущности по ID

Первый способ загрузки объекта через Entities API – это загрузка по идентификатору. Соответствующей для этого операцией является конечная точка Load Entity /entities/:entityName/:entityId.

Параметр пути :entityName определяет тип сущности. Значение задается в определении сущности:

Order.java
@JmixEntity
@Table(name = "SAMPLE_ORDER")
@Entity(name = "sample_Order") (1)
public class Order {
    // ...
}
1 Значение атрибута имени sample_Order аннотации JPA @Entity указывает параметр entityName в Entities API.

Операция Load Entity возвращает один экземпляр, если он найден по своему идентификатору. В противном случае возвращается код состояния HTTP 404 - Not Found.

Load Order Request
GET http://localhost:8080/rest
            /entities
            /sample_Order
            /21021f78-edac-224b-e6f8-6e71e02a0f0d
Response: 200 - OK
{
  "_entityName": "sample_Order", (1)
  "_instanceName": "rest.sample.entity.Order-21021f78-edac-224b-e6f8-6e71e02a0f0d [detached]",
  "id": "21021f78-edac-224b-e6f8-6e71e02a0f0d",
  "date": "2020-12-13", (2)
  "amount": 49.99,
  "createdDate": "2021-02-06T12:03:38.049",
  "createdBy": "admin",
  "lastModifiedDate": "2021-02-06T12:03:38.049",
  "version": 1
}
1 Некоторые метаданные об экземпляре сущности возвращаются в виде ключей JSON (entityName, _instanceName и id).
2 Объект JSON содержит каждый бизнес-атрибут в качестве ключа.

Если сущность имеет составной идентификатор, передавайте его при вызове REST в виде JSON, закодированного в Base64.

Например, если класс ID содержит два поля code1 и code2, то значение идентификатора в JSON будет выглядеть как {"code1": "val1","code2": "val2"}, а весь URL с закодированным идентификатором так:

GET http://localhost:8080/rest
            /entities
            /MyEntity
            /eyJjb2RlMSI6ICJ2YWwxIiwiY29kZTIiOiAidmFsMiJ9

Некоторые атрибуты сущности Order отсутствуют в JSON, так как по умолчанию загружаются только локальные и постоянные атрибуты. В случае, если ссылочные атрибуты также должны быть включены в ответ, используйте фетч-планы, как описано ниже.

Если вы хотите включить в ответ динамические атрибуты, используйте параметр dynamicAttributes, чтобы добавить в запрос специальную подсказку:

GET http://localhost:8080/rest
            /entities
            /sample_Order
            /21021f78-edac-224b-e6f8-6e71e02a0f0d
            ?dynamicAttributes=true

Использование фетч-планов

Необходимые атрибуты обычно зависят от варианта использования клиента, пользовательского интерфейса или сценария интеграции. Часто загрузки только прямых атрибутов сущности недостаточно.

Если каждый тип сущности возможно загружать только в отдельном запросе, в последующих запросах потребовалось бы загружать информацию о сущности, на которую дана ссылка. Это привело бы к проблемам с запросами N+1 во всем приложении. В частности, при взаимодействии через HTTP такая нагрузка может стать огромной.

Поэтому Load Entities API также поддерживает использование фетч-планов. С помощью них можно настроить дерево атрибутов, которые должны загружаться из базы данных вместе, одним пакетом, и передаваться клиенту через Load Entities API.

В следующем примере рассмотрим, как загрузить список заказов (Order) с дополнительной информацией о соответствующих клиентах (Customer), связанных строках заказа и даже информацией о продукте соответствующей линейки.

Во-первых, необходимо зарегистрировать фетч-план order-with-details в файле конфигурации fetch-plans.xml:

fetch-plans.xml
<fetchPlans xmlns="http://jmix.io/schema/core/fetch-plans">
    <fetchPlan class="rest.sample.entity.Order"
               extends="_base"
               name="order-with-details">
        <property name="customer"/>
        <property name="lines" fetchPlan="_base">
            <property name="product" fetchPlan="_instance_name" />
        </property>
    </fetchPlan>
</fetchPlans>

Имея эту конфигурацию, вы можете выполнить запрос и сослаться на фетч-план через параметр URL fetchPlan.

В приведенном ниже примере Order с идентификатором 21021f78-edac-224b-e6f8-6e71e02a0f0d загружается с планом выборки order-with-details, чтобы дополнительно загрузить данные customer и lines:

Load Order with Fetch Plan Request
GET http://localhost:8080/rest
            /entities
            /sample_Order
            /21021f78-edac-224b-e6f8-6e71e02a0f0d
            ?fetchPlan=order-with-details
Response: 200 - OK
{
  "id": "21021f78-edac-224b-e6f8-6e71e02a0f0d",
  "date": "2020-12-13",
  "amount": 49.99,
  "lines": [ (1)
    {
      "id": "64e4fbb0-7fd6-818b-984e-a8769c4fbe88",
      "product": {
        "id": "7750adbe-6c30-cede-31a6-577a1a96aa83",
        "name": "Outback Power Remote Power System"
      },
      "quantity": 1.0
    }
  ],
  "version": 1,
  "customer": {
    "id": "0826806e-6074-90fa-f241-564b5c94d018",
    "name": "Sidney Chandler",
  }
}
1 Фетч-план order-with-details гарантирует, что включены дополнительные атрибуты, такие как lines и customer.

Загрузка списка сущностей

Вы можете загрузить список сущностей любого типа, используя операцию Load Entity List API: /entities/:entityName. Этот API включает в себя разбивку на страницы, сортировку и фетч-планы.

Request
GET http://localhost:8080/rest/entities/sample_Customer
Response: HTTP 200 - OK
[
  {
    "id": "0826806e-6074-90fa-f241-564b5c94d018",
    "name": "Sidney Chandler"
  },
  {
    "id": "22efc597-69a9-aeef-4e4a-7afccd8e5767",
    "name": "Randall Bishop"
  },
  {
    "id": "bd1c8e90-3d35-cbe2-9efd-167202c758d2",
    "name": "Shelby Robinson"
  }
]
Каждая сущность в ответе имеет атрибут _entityName с именем сущности и атрибут _instanceName с именем экземпляра сущности.

Также можно дополнительно проконтролировать поведение API, используя следующие параметры URL-запроса:

dynamicAttributes

следует ли загружать динамические атрибуты для сущности (Boolean).

fetchPlan

имя фетч-плана (String).

limit

количество сущностей, которое должен вернуть API (int).

offset

позиция первой возвращенной сущности (int).

sort

атрибут сущности, которая будет использоваться для сортировки (String).

  • +attribute или просто attribute для порядка по возрастанию.

  • -attribute для порядка по убыванию.

Использование сортировки

Load Entities API поддерживает сортировку результата по атрибутам сущности. Для управления порядком сущностей используется параметр URL sort.

Если параметр sort не указан, порядок сортировки по умолчанию зависит от реализации базы данных. Обычно базы данных сортируют по времени создания записи, но это поведение не гарантировано и может отличаться в разных ситуациях.

Jmix имеет специальный синтаксис для определения порядка сортировки. Порядок по возрастанию выражается через + перед именем атрибута, что необязательно, так как это поведение по умолчанию. Для порядка по убыванию необходимо поставить перед атрибутом сущности символ -.

В следующем примере показано, как можно отсортировать клиентов (Customer) по возрастанию их атрибутов name.

Request
GET http://localhost:8080/rest
            /entities
            /sample_Customer
            ?sort=name
Response: HTTP 200 - OK
[
  {
    "id": "d83c9d66-cb23-075a-8d3c-d4035d338705",
    "name": "Klaudia Kleinert"
  },
  {
    "id": "8985ba1e-1cc8-eb5c-f9e0-738aee9d2ef1",
    "name": "Randall Bishop"
  }
]

Можно также сортировать по нескольким атрибутам. В этом случае порядок сортировки принимает список атрибутов, разделенных запятыми.

Request
GET http://localhost:8080/rest
            /entities
            /sample_Order?sort=+date,-amount
Response: HTTP 200 - OK
[
  {
    "id": "41aae331-b46b-85ee-b0bc-2de8cbf1ab86",
    "date": "2021-02-02", (1)
    "amount": 283.55
  },
  {
    "id": "288a5d75-f06f-d150-9b70-efee1272b96c",
    "date": "2021-03-01",
    "amount": 249.99, (2)
    "lastModifiedBy": "admin"
  },
  {
    "id": "1068c217-5868-faf4-16aa-23655e9492da",
    "date": "2021-03-01",
    "amount": 130.08
  }
]
1 Первым возвращается результат с самой старой датой.
2 Когда атрибут date одинаков, для сортировки результатов используется amount.

Использование разбивки на страницы

Entities API поддерживает разбивку на страницы для соблюдения ограничений обработки данных, которые могут присутствовать на стороне сервера или клиента. Если вы хотите загрузить только определенное подмножество сущностей, можно указать параметры URL offset и limit .

Разбивка на страницы активна по умолчанию, даже если она не запрошена клиентом явно. Если в запросе не указано значение limit, Load API вернут только первые 10,000 сущностей.

Это значение по умолчанию настраивается глобально в jmix.rest.default-max-fetch-size или для каждого сущности отдельно в jmix.rest.entityMaxFetchSize.

В следующем примере показано, как загрузить третью страницу, содержащую две сущности Customer (5. и 6.):

Load Customer Request with Pagination
GET http://localhost:8080/rest
            /entities
            /sample_Customer
            ?limit=2
            &offset=4
            &sort=createdDate
Response: HTTP 200 - OK
[
  {
    "id": "2d620164-1e80-0696-c3aa-45b7b5c81f2c",
    "name": "Maria Mitchell"
  },
  {
    "id": "3c7ec69d-9b85-c6e9-387b-42a5bccb79de",
    "name": "Anthony Knutson"
  }
]

Загрузка сущностей через фильтр поиска

Вы можете указать критерии фильтрации при загрузке сущностей с помощью операции Entity Search: /entities/:entityName/search.

При взаимодействии с операцией поиска возможно использовать оба метода HTTP GET и POST. В обоих случаях критерий фильтрации должен быть предоставлен как часть запроса.

Определение фильтра — это структура JSON, содержащая набор условий. Условие состоит из следующих атрибутов:

property

атрибут объекта, по которому проводится фильтрация (например, amount сущности Order).

Если атрибут является ссылкой на другую сущность, он также может быть путем к свойству, например customer.name

operator

оператор фильтра. Оператор описывает, как фильтровать определенный атрибут. Существует несколько операторов, которые можно использовать независимо от типа данных:

  • Стандартные операторы: =, <>, notEmpty, isNull

  • Операторы списка: in, notIn

Кроме того, некоторые операторы возможны только для определенных типов данных:

Datatype Специальные операторы

String, UUID

startsWith, endsWith, contains, doesNotContain

Integer, Long, Double, BigDecimal, Date, DateTime, Time, LocalDate, LocalDateTime, LocalTime, OffsetDateTime, OffsetTime

=, <>, >, >=, <, <=

value

значение для поиска. Не требуется для операторов notEmpty и isNull.

Помимо этого условия можно комбинировать с помощью групповых условий AND и OR для определения более сложного критерия фильтрации. JSON-структура определений фильтров выглядит следующим образом:

Filter Criterion JSON structure
{
  "conditions": [
    {
      "group": "OR",
      "conditions": [
        {
          "property": "stringField",
          "operator": "=",
          "value": "stringValue"
        },
        {
          "property": "intField",
          "operator": ">",
          "value": 100
        }
      ]
    },
    {
      "property": "booleanField",
      "operator": "=",
      "value": true
    }
  ]
}

Это представление критерия фильтра ((stringField = stringValue) OR (intField > 100) AND (booleanField = true)).

При использовании метода HTTP POST фильтр является частью тела запроса.

Filter POST Request
POST http://localhost:8080/rest/entities/sample_Order/search

{
  "filter": {
    "conditions": [
      {
        "property": "customer.name",
        "operator": "=",
        "value": "Shelby Robinson"
      }
    ]
  }
}

При использовании метода GET критерий фильтра JSON необходимо передавать через параметр URL-запроса filter.

Filter GET Request
GET http://localhost:8080/rest
            /entities
            /sample_Order
            /search
            ?filter={"conditions":[{"property":"customer.name","operator":"contains","value":"Shelby"}]}
Кодировка URI

Стандарт HTTP URI допускает символы ASCII только как часть URI/URL. При использовании параметров URL-запроса для определения фильтра определение JSON должно быть URL-закодировано, чтобы соответствовать этому требованию. Это также верно в случае данных value, которые обычно содержат пользовательский ввод.

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

Загрузка сущностей через JPQL

Другой альтернативой загрузке сущностей из приложения является использование предопределенных запросов JPQL. За предоставление этой возможности отвечает операция Entity Query /queries/:entityName/:queryName. Запросы могут содержать список параметров, которые должен предоставить клиент. Кроме того, конечная точка содержит все те же общие параметры для разбивки на страницы, фетч-планы и т. д.

Когда использовать JPQL вместо фильтра поиска?

Jmix предоставляет различные способы общей загрузки данных сущности. Используйте предопределенные запросы JPQL, если фильтр поиска недостаточно продвинут для выражения критерия фильтра, а также в случае, если параметр должен быть предопределен и не может быть изменен клиентом API.

Конфигурация запроса JPQL

Чтобы использовать операцию Entity Query, необходимо определить доступные запросы через файл конфигурации XML, обычно называемый rest-queries.xml. Создайте этот новый файл в вашем приложении Jmix в src/main/resources. В нем перечисляются все опубликованные запросы с информацией об их параметрах.

rest-queries.xml
<?xml version="1.0"?>
<queries xmlns="http://jmix.io/schema/rest/queries">
    <query name="ordersByDate" entity="sample_Order" fetchPlan="order-with-details">
        <jpql><![CDATA[select e from sample_Order e where e.date = :orderDate]]></jpql>
        <params>
            <param name="orderDate" type="java.time.LocalDate"/>
        </params>
    </query>
    <query name="ordersByCustomerName" entity="sample_Order" fetchPlan="order-with-details">
        <jpql><![CDATA[select e from sample_Order e where e.customer.name = :customerName]]></jpql>
        <params>
            <param name="customerName" type="java.lang.String"/>
        </params>
    </query>
</queries>

Запрос должен иметь уникальное значение name, а также ссылку entity. Сочетание name и entity должно быть уникальным. Кроме того, необходима ссылка на fetchPlan, чтобы указать возвращаемые атрибуты сущности.

В теге <jpql> конфигурируется сам запрос. Параметры должны быть перечислены внутри тега params, определяющего их имя и тип Java. В параметрах запроса можно ссылаться через их имя с префиксом двоеточия, например :customerName.

После создания файла и определения запросов необходимо зарегистрировать конфигурацию rest-queries.xml в application.properties вашего приложении Jmix:

application.properties
jmix.rest.queries-config = rest/sample/rest-queries.xml

Операцию Entity Query можно вызвать с помощью HTTP метода GET или POST. В случае выбора GET параметры добавляются как параметры URL-запроса.

Query API GET Request
GET http://localhost:8080/rest
        /queries
        /sample_Order
        /ordersByDate
        ?orderDate=2020-02-02
URI-Кодировка

URL-адрес должен содержать только символы ASCII. Это означает, что значения параметров должны быть URL-закодированы, поскольку эти значения обычно представляют собой прямой пользовательский ввод, и гарантировать использование отличных от ASCII символов невозможно.

В случае использования POST параметры запроса передаются в теле JSON, содержащем каждый параметр в качестве ключа.

Query API POST Request
POST http://localhost:8080/rest/queries/sample_Order/ordersByCustomerName

{
  "customerName": "Shelby Robinson"
}

Параметры коллекции

Также возможно определить параметр как тип коллекции. В этом случае определение запроса должно содержать индикатор [] после типа Java.

rest-queries.xml
<?xml version="1.0"?>
<queries xmlns="http://jmix.io/schema/rest/queries">
    <query name="ordersByIds" entity="sample_Order" fetchPlan="order-with-details">
        <jpql><![CDATA[select e from sample_Order e where e.id in :ids]]></jpql>
        <params>
            <param name="ids" type="java.util.UUID[]"/> (1)
        </params>
    </query>
</queries>
1 Параметр ids помечен как коллекция типа UUID.

Когда этот параметр используется в запросе, соответствующие идентификаторы должны быть предоставлены в виде массива JSON.

Query API Collection Parameters Request
POST http://localhost:8080/rest/queries/sample_Order/ordersByIds

{
  "ids": [
    "41aae331-b46b-85ee-b0bc-2de8cbf1ab86",
    "21021f78-edac-224b-e6f8-6e71e02a0f0d"
  ]
}

Возврат пустых значений в JSON

По умолчанию Jmix удаляет пустые значения (null) из ответа JSON, чтобы ключи атрибутов не присутствовали в документе JSON.

Вы можете управлять этим поведением, используя параметр URL-запроса returnNulls и присвоив ему значение true. Тогда Jmix будет всегда добавлять ключи атрибутов к ответу, независимо от того, является ли значение пустым или нет.

В следующем примере Customer загружается по его идентификатору, а также запрашиваются все пустые значения:

Load Customer with empty values
GET http://localhost:8080/rest
            /entities
            /sample_Customer
            /1eab4973-25f9-70d9-5356-6990dd8f79e2
            ?returnNulls=true
Response: 200 - OK
{
  "_entityName": "sample_Customer",
  "_instanceName": "Sidney Chandler",
  "id": "0826806e-6074-90fa-f241-564b5c94d018",
  "createdDate": "2021-06-09T08:42:39.291",
  "createdBy": "admin",
  "lastModifiedDate": "2021-06-09T08:42:39.291",
  "deletedDate": null,
  "lastModifiedBy": null,
  "name": "Sidney Chandler",
  "type": null, (1)
  "version": 1,
  "deletedBy": null
}
1 Ответ содержит ключ type, хотя тот пуст
Параметр returnNulls присутствует во всех Entity Load API: Загрузка по ID, Загрузка списка, Поиск и загрузка по запросу.