加载实体

实体 API 支持以多种方式加载实体:

用 ID 加载实体

第一个加载实体的方法就是用实体的 ID 加载单一实体。对应的 实体加载 接口地址是:/entities/:entityName/:entityId

:entityName 路径参数定义实体名称,实体名称在定义实体时提供:

Order.java
@JmixEntity
@Table(name = "SAMPLE_ORDER")
@Entity(name = "sample_Order") (1)
public class Order {
    // ...
}
1 JPA @Entity 注解的 name 属性指定的 sample_Order 值可作为实体 API 中 entityName 参数的值。

如果有此 ID 的实体实例,则 实体加载 接口返回此实例。否则返回 HTTP 状态码 404 - Not Found

加载单一 Order 实体
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_instanceNameid)。
2 JSON 对象中其他业务属性键值。

并非 Order 实体的全部属性都在 JSON 中加载,默认仅加载实体的本地和持久化属性。如需在响应中包含实体的引用属性,则需要使用 Fetch Plan

如需在响应中包含 动态属性,使用 dynamicAttributes 参数:

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

使用 Fetch Plan

根据客户的用例、UI 或集成场景不同,需要加载的实体属性通常也会有所不同。因此,仅加载实体的直接属性通常是不够的。

由于每种实体类型只能在专门的请求中加载,所以需要在后续请求中加载实体引用的信息。然而,这将导致整个应用程序中出现 N+1 查询问题。特别是当通过 HTTP 进行交互时,相应的开销可能会非常大。

为此,实体加载 API 也支持 Fetch Plan 的概念。使用 Fetch plan 可以配置一次从数据库需要加载的属性树,并通过实体加载 API 传递给客户端。

下面的示例展示如何加载 Orders 实体及其相关的 customer、order lines 信息,甚至 order line 的 product 信息。

首先,需要在 fetch-plans.xml 配置文件中注册 order-with-details

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>

Fetch plan 配置好后,可以用 URL 参数 fetchPlan 使用并发起请求。

下面示例中 ID 为 21021f78-edac-224b-e6f8-6e71e02a0f0d 的 Order 通过 fetch plan order-with-details 进行加载,此时会加载附加的 customerlines 数据:

使用 Fetch plan 请求加载 Order
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 Fetch plan order-with-details 保证了结果包含额外的属性 linescustomer

加载实体列表

使用 加载实体列表 API 接口 /entities/:entityName 可以加载任何类型的实体列表。此 API 包含分页、排序和 fetch plan 参数。

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 属性,表示 实例名称

还可以用下列 URL 查询参数进一步控制 API 的行为:

dynamicAttributes

是否加载实体的 动态属性 (Boolean)

fetchPlan

实体的 fetch plan 名称 (String)

limit

限制 API 获取实例的个数 (int)

offset

查询第一个实体的偏移量 (int)

sort

用于排序的实体属性 (String)

  • +attributeattribute 用于正排序;

  • -attribute 用于倒排序。

使用排序

加载实体列表 API 支持使用实体属性对查询结果进行排序。用 sort URL 参数指定实体属性排序。

当未指定 sort 参数时,默认排序依赖数据库的实现。通常数据库会按照实体创建的时间戳进行排序,但是这个机制在不同场景也不是绝对有效。

Jmix 使用特殊的语法定义排序。正排序通过属性名前可选的 + 号表示。默认是正排序。- 号表示按属性名倒排序。

下面示例展示如何使用 name 属性对 Customer 进行正排序。

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 倒排序。

使用分页

加载实体列表 API 支持对数据进行分页,以便适应服务端或客户端的数据处理能力。如果只需要加载部分实体列表,可以给请求提供 offsetlimit 参数。

分页是默认开启的,即使客户端没有显式的加上分页请求。如果请求没有 limit 参数,API 返回前 10,000 个实体。

默认值通过 jmix.rest.default-max-fetch-size 参数进行全局配置,或者通过 jmix.rest.entityMaxFetchSize 参数在实体级别配置。

下面示例中展示如何加载包含两个 Customer 实体的第 3 页数据(第 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"
  }
]

用过滤条件加载实体

当使用 实体搜索 接口 /entities/:entityName/search 加载实体列表时,可以指定过滤条件。

该接口支持使用 GETPOST 两种 HTTP 方法。两种方法中,过滤条件都需要作为请求的一部分提供。

过滤条件通过 JSON 结构定义,可以包含一组过滤条件。一个条件可以包含下列属性:

property

需要过滤的实体属性(例如 Order 实体的 amount 属性)。

如果该属性是实体引用,则支持属性路径,例如,customer.name

operator

过滤器操作符。操作符描述如何对属性进行过滤。对不同的数据类型,有多个可使用的操作符:

  • 标准操作符:=<>notEmptyisNull

  • 列表操作符:innotIn

以及,某些操作符只能用于特定数据类型:

数据类型 特定操作符

String, UUID

startsWithendsWithcontainsdoesNotContain

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

=<>>>=<<=

value

需要搜索的值,对于 notEmptyisNull 操作符,不需要提供值。

条件可以用 ANDOR 进行组合,以便定义复杂的过滤条件。过滤条件的 JSON 结构示例:

过滤条件 JSON 结构
{
  "conditions": [
    {
      "group": "OR",
      "conditions": [
        {
          "property": "stringField",
          "operator": "=",
          "value": "stringValue"
        },
        {
          "property": "intField",
          "operator": ">",
          "value": 100
        }
      ]
    },
    {
      "property": "booleanField",
      "operator": "=",
      "value": true
    }
  ]
}

这是此过滤条件的 JSON 展示:((stringField = stringValue) OR (intField > 100) AND (booleanField = true))

当使用 HTTP POST 方法请求时,过滤条件作为请求体提供:

POST 过滤条件请求
POST http://localhost:8080/rest/entities/sample_Order/search

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

当使用 GET 方法时,JSON 过滤条件需要通过 URL 查询参数 filter 提供:

GET 过滤条件请求
GET http://localhost:8080/rest
            /entities
            /sample_Order
            /search
            ?filter={"conditions":[{"property":"customer.name","operator":"contains","value":"Shelby"}]}
URI 编码

HTTP URI 标准仅允许在 URI/URL 中使用 ASCII 编码的字符。因此,当使用 URL 查询参数过滤条件时,JSON 定义需要用 URL 编码。对于 value 提供的数据也是一样的要求。

另外,还有一个对 URI 长度实际的限制也会导致使用复杂过滤条件的问题。所以,推荐使用 POST 方法对实体列表进行过滤,可避免受到这些限制的影响。

用 JPQL 加载实体

从应用程序加载实体的另一个方法就是使用预定义的 JPQL 查询语句。实体查询 接口 /queries/:entityName/:queryName 提供此功能。查询语句可以包含一组由客户端负责提供的参数。此外,接口也可以使用通用的分页、fetch plan 等参数。

何时该用 JPQL 或 实体搜索

Jmix 提供了多种加载实体列表的方式。当过滤条件参数不方便表示实际所需的条件,或者条件参数需要预先定义而非调用时由客户端提供,请使用预定义 JPQL 查询。

JPQL 查询配置

如需使用 实体查询 接口,需要先定义可用的查询语句。查询语句通过 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 引用属性。要求 nameentity 的组合值必须唯一。此外,fetchPlan 属性指定加载实体的哪些属性。

jpql 元素中配置查询语句。查询参数在 params 元素中定义名称和 Java 类型。JPQL 语句中可以用冒号加参数名的方式引用参数,例如,:customerName

在文件和查询语句都创建好之后,需要在 Jmix 的 application.properties 文件中注册 rest-queries.xml 配置:

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

实体查询 接口支持通过 GETPOST 请求调用。使用 GET 时,查询参数在 URL 中提供:

GET 查询 API 调用
GET http://localhost:8080/rest
        /queries
        /sample_Order
        /ordersByDate
        ?orderDate=2020-02-02
URI 编码

HTTP URI 标准仅允许在 URI/URL 中使用 ASCII 编码的字符。因此,当使用 URL 查询参数过滤条件时,JSON 定义需要用 URL 编码。对于 value 提供的数据也是一样的要求。

使用 POST 时,查询参数在 JSON 请求体中提供,每个 key 表示一个参数:

POST 查询 API 调用
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 集合类型。

在调用端传递该参数时,对应的 ID 需要用 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 会从 JSON 返回体中删除空值(null),这样空值属性的 key 就不会在 JSON 中出现。

可以用 URL 查询参数 returnNulls 控制该行为。如果设置为 true,Jmix 则会在 JSON 中添加空值属性的 key。

下面示例中,使用 ID 加载了一个 customer,并请求了所有的空值属性:

加载带空值属性的 Customer
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 可以用在所有的实体加载 API 中:按 ID 加载,加载列表,搜索实体和查询实体。