3.9.11.15. 数据模型版本化示例
实体属性被重命名




我们假设 sales$Order 实体的 oldNumber 属性被重命名为 newNumber 并且 date 被重命名为 deliveryDate。在这种情况下,转换配置将如下所示:





  1. <?xml version="1.0"?>
    <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.0" currentEntityName="sales$Order">
    <renameAttribute oldName="oldNumber" currentName="newNumber"/>
    <renameAttribute oldName="date" currentName="deliveryDate"/>
    </transformation>

    </transformations>






如果客户端应用程序需要使用旧版的 sales$Order 实体,那么它必须在 URL 参数中传递 modelVersion 值:



http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.0



将返回以下结果:





  1. {
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "date": "2016-10-31",
    "description": "Vacation order",
    "oldNumber": "00001"
    }






响应 JSON 包含 oldNumberdate 属性,尽管 CUBA 应用程序中的实体具有 newNumberdeliveryDate 属性。



实体名称被更改




接下来,试想一下,在应用程序的某个版本中,sales$Order 实体的名称也发生了变化。新名称是 sales$NewOrder



版本 1.1 的转换配置将如下所示:





  1. <?xml version="1.0"?>
    <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.1" oldEntityName="sales$Order" currentEntityName="sales$NewOrder">
    <renameAttribute oldName="oldNumber" currentName="newNumber"/>
    </transformation>

    </transformations>






除了上一个示例中的配置之外,还在此处添加了 oldEntityName 属性。它指定对模型版本 1.1 有效的实体名称。currentEntityName 属性指定当前实体名称。



虽然名称为 sales$Order 的实体不再存在,但以下请求也可以正常运行:



http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1



REST API 控制器将认为它必须在 sales$NewOrder 实体里进行搜索,并且在找到具有给定 id 的实体之后,在结果 JSON 中替换实体的名称和 newNumber 属性的名称:





  1. {
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "date": "2016-10-31",
    "description": "Vacation order",
    "oldNumber": "00001"
    }






客户端应用程序也可以使用旧版本的数据模型进行实体更新和创建。



POST 请求使用旧实体名称,并且在请求体使用旧的 JSON,这个 POST 请求可以正常工作:



http://localhost:8080/app/rest/v2/entities/sales$Order





  1. {
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "date": "2016-10-31",
    "description": "Vacation order",
    "oldNumber": "00001"
    }






必须从 JSON 中删除的实体属性




如果某个属性已添加到实体,但使用旧版数据模型的客户端不希望有此新属性,则可以从结果 JSON 中删除新属性。



此示例的转换配置如下所示:





  1. <?xml version="1.0"?>
    <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.5" currentEntityName="sales$Order">
    <toVersion>
    <removeAttribute name="discount"/>
    </toVersion>
    </transformation>

    </transformations>






此配置文件中的转换包含带有嵌套 removeAttribute 命令的 toVersion 标记。这意味着当执行从当前状态到特定版本的转换时(即当请求实体列表时),必须从结果 JSON 中删除 discount 属性。



在这种情况下,如果在没有 modelVersion 属性的情况下执行请求,discount 属性将会被返回:



http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93





  1. {
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "deliveryDate": "2016-10-31",
    "description": "Vacation order",
    "number": "00001",
    "discount": 50
    }






如果指定 modelVersion,则将删除 discount 属性



http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1





  1. {
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "deliveryDate": "2016-10-31",
    "description": "Vacation order",
    "oldNumber": "00001"
    }






使用自定义转换器




还可以创建和注册自定义 JSON 转换器。举个例子,我们来看看下面的情况:有一个实体 sales$OldOrder 被重命名为 sales$NewOrder。该实体具有 orderDate 字段。在之前的版本中,此日期字段包含时间部分,但在最新版本的实体中,时间部分将被删除。请求旧模型版本 1.0 的实体的 REST API 客户端期望日期字段有时间部分,因此转换器必须修改 JSON 中的值。



首先,转换器配置必须如下所示:





  1. <?xml version="1.0"?>
    <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">

    <transformation modelVersion="1.0" oldEntityName="sales$OldOrder" currentEntityName="sales$NewOrder">
    <custom>
    <fromVersion transformerBeanRef="sales_OrderJsonTransformerFromVersion"/>
    <toVersion transformerBeanRef="sales_OrderJsonTransformerToVersion"/>
    </custom>
    </transformation>


    </transformations>






有一个 custom 元素和嵌套的 toVersionfromVersion 元素。这些元素引用了转换器 bean。这意味着自定义转换器必须注册为 Spring bean。这里有一件重要的事情:自定义转换器可以使用 RestTransformations 平台 bean(如果需要,这个 bean 可以访问其它实体转换器)。但是 RestTransformations bean 在 REST API servlet 的 Spring 上下文中注册,而不是在 Web 应用程序的主上下文中注册。这意味着自定义转换器 bean 也必须在 REST API Spring 上下文中注册。



我们怎样做到这一点?



首先,在 webportal 模块中创建一个 rest-dispatcher-spring.xml (例如,在 com.company.test 包中)。



接下来,在 Web 或 portal 模块的 app.properties 中注册此文件:





  1. cuba.restSpringContextConfig = +com/company/test/rest-dispatcher-spring.xml






rest-dispatcher-spring.xml 必须包含自定义转换器 bean 定义:





  1. <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean name="sales_OrderJsonTransformerFromVersion" class="com.company.test.transformer.OrderJsonTransformerFromVersion"/>
    <bean name="sales_OrderJsonTransformerToVersion" class="com.company.test.transformer.OrderJsonTransformerToVersion"/>

    </beans>






sales_OrderJsonTransformerToVersion 转换器的内容如下:





  1. package com.company.test.transformer;

    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.google.common.base.Strings;
    import com.haulmont.restapi.transform.AbstractEntityJsonTransformer;
    import com.haulmont.restapi.transform.JsonTransformationDirection;

    public class OrderJsonTransformerToVersion extends AbstractEntityJsonTransformer {

    public OrderJsonTransformerToVersion() {
    super("sales$NewOrder", "sales$OldOrder", "1.0", JsonTransformationDirection.TO_VERSION);
    }

    @Override
    protected void doCustomTransformations(ObjectNode rootObjectNode, ObjectMapper objectMapper) {
    JsonNode orderDateNode = rootObjectNode.get("orderDate");
    if (orderDateNode != null) {
    String orderDateNodeValue = orderDateNode.asText();
    if (!Strings.isNullOrEmpty(orderDateNodeValue))
    rootObjectNode.put("orderDate", orderDateNodeValue + " 00:00:00.000");
    }
    }
    }






此转换器在 JSON 对象中找到 orderDate 节点,并通过将时间部分添加到该值来修改该值。



当请求数据模型版本 1.0sales$OldOrder 实体时,结果 JSON 将包括含有时间部分的 orderDate 字段的实体,尽管它不再存储在数据库中。



关于定制转换器的更多内容:它们必须实现 EntityJsonTransformer 接口。还可以继承 AbstractEntityJsonTransformer 类并覆盖其 doCustomTransformations 方法。AbstractEntityJsonTransformer 类包含标准转换器的所有功能。