java - 使用 Spring Data REST 处理子实体集合中的资源链接

标签 java spring-data-rest hateoas spring-hateoas

我正在评估 Spring Data REST 作为基于 AngularJS 的应用程序的后端。我快速将我们的域建模为一组聚合根,并遇到了以下设计障碍:

  • 模型资源具有
  • 多个任务实体
  • 引用多个属性资源

我希望将属性的 HAL _links 放置在每个任务 JSON 对象内,但遗憾的是这些属性仅作为 JSON 构造根部的链接可见。

例如我明白了:

{
  "version": 0,
  "name": "myModel",
  "tasks": [
    {
      "name": "task1"
    },
    {
      "name": "task2"
    }
  ],
  "_links": {
    "self": {
      "href": "http://localhost:8080/models/1"
    },
    "attributes": {
      "href": "http://localhost:8080/models/1/attributes"
    }
  }
}

我想象的可能是这样的:

{
  "version": 0,
  "name": "myModel",
  "tasks": [
    {
      "name": "task1",
      "_links": {
        "attributes": {
        "href": "http://localhost:8080/models/1/tasks/1/attributes"
      }
  }
    },
    {
      "name": "task2",
      "_links": {
        "attributes": {
        "href": "http://localhost:8080/models/1/tasks/2/attributes"
      }
    }
  ],
  "_links": {
    "self": {
      "href": "http://localhost:8080/models/1"
    },
    "attributes": {
      "href": "http://localhost:8080/models/1/attributes"
    }
  }
}

顺便说一句,在第一个示例中,属性链接以 404 结尾。

我没有在 HAL 规范中看到任何处理这种情况的内容,也没有在 Spring Data REST 文档中看到任何内容。显然,我可以将任务定义为解决问题的资源,但我的模型不需要这样做。我觉得这是一个合法的用例。

我创建了一个简单的 Spring Boot 应用程序来重现此问题。型号:

@Entity
public class Model {

    @Id @GeneratedValue public Long id;
    @Version public Long version;

    public String name;

    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    public List<Task> tasks;

}

@Entity
public class Task {

    @Id @GeneratedValue public Long id;

    public String name;

    @ManyToMany
    public Set<Attribute> attributes;

}

@Entity
public class Attribute {

    @Id @GeneratedValue public Long id;
    @Version public Long version;

    public String name;
}

和存储库:

@RepositoryRestResource
public interface ModelRepository extends PagingAndSortingRepository<Model, Long> {
}

@RepositoryRestResource
public interface AttributeRepository extends PagingAndSortingRepository<Attribute,Long> {
}

在那里,我可能错过了一些东西,因为这似乎是一个非常简单的用例,但找不到任何有类似问题的人。另外,也许这是我的模型中的一个根本缺陷,如果是这样,我准备听听你的论点:-)

最佳答案

由于 Spring Data REST 本身不处理问题中描述的用例,因此第一步是停用任务属性的管理,并确保默认情况下它们不会被序列化。这里是@RestResource(exported=false)确保不会为“属性”rel 自动生成(非工作)链接,并且 @JsonIgnore确保默认情况下不会渲染属性。

@Entity
public class Task {
    @Id
    @GeneratedValue
    public Long id;

    public String name;

    @ManyToMany
    @RestResource(exported = false)
    @JsonIgnore
    public List<Attribute> attributes;
}

接下来,_links属性仅在我们资源的根部可用,因此我选择实现一个名为“taskAttributes”的新关系,它将有多个值,每个值对应一个任务。为了将这些链接添加到资源,我构建了一个自定义 ResourceProcessor ,并实现实际端点,自定义 ModelController :

@Component
public class ModelResourceProcessor implements ResourceProcessor<Resource<Model>> {

    @Override
    public Resource<Model> process(Resource<Model> modelResource) {
        Model model = modelResource.getContent();
        for (int i = 0; i < model.tasks.size(); i++) {
            modelResource.add(linkTo(ModelController.class, model.id)
                    .slash("task")
                    .slash(i)
                    .slash("attributes")
                    .withRel("taskAttributes"));
        }
        return modelResource;
    }
}

@RepositoryRestController
@RequestMapping("/models/{id}")
public class ModelController {

    @RequestMapping(value = "/task/{index}/attributes", method = RequestMethod.GET)
    public ResponseEntity<Resources<PersistentEntityResource>> taskAttributes(
            @PathVariable("id") Model model,
            @PathVariable("index") int taskIndex,
            PersistentEntityResourceAssembler assembler) {
        if (model == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        if (taskIndex < 0 || taskIndex >= model.tasks.size()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        List<Attribute> attributes = model.tasks.get(taskIndex).attributes;

        List<PersistentEntityResource> resources = attributes.stream()
                .map(t -> assembler.toResource(t))
                .collect(toList());

        return ResponseEntity.ok(new Resources(resources));
    }
}

这会调用 http://localhost:8080/api/models/1返回类似这样的内容:

{
  "name": "myModel",
  "tasks": [
    {
      "name": "task1"
    },
    {
      "name": "task2"
    }
  ],
  "_links": {
    "self": {
      "href": "http://localhost:8080/models/1"
    },
    "model": {
      "href": "http://localhost:8080/models/1{?projection}",
      "templated": true
    },
    "taskAttributes": [
      {
        "href": "http://localhost:8080/models/1/task/0/attributes"
      },
      {
        "href": "http://localhost:8080/models/1/task/1/attributes"
      }
    ]
  }
}

最后,为了使所有这些在 UI 中更可用,我在模型资源上添加了一个投影:

@Projection(name = "ui", types = {Model.class, Attribute.class})
public interface ModelUiProjection {
    String getName();
    List<TaskProjection> getTasks();

    public interface TaskProjection {
        String getName();
        List<AttributeUiProjection> getAttributes();
    }
    public interface AttributeUiProjection {
        String getName();
    }
}

这可以让我们获得属性属性的子集,而无需从“taskAttributes”相关中获取它们:

http://localhost:8080/api/models/1?projection=ui返回类似这样的内容:

{
  "name": "myModel",
  "tasks": [
    {
      "name": "task1",
      "attributes": [
        {
          "name": "attrForTask1",
          "_links": {
            "self": {
              "href": "http://localhost:8080/attributes/1{?projection}",
              "templated": true
            }
          }
        }
      ]
    },
    {
      "name": "task2",
      "attributes": [
        {
          "name": "attrForTask2",
          "_links": {
            "self": {
              "href": "http://localhost:8080/attributes/2{?projection}",
              "templated": true
            }
          }
        },
        {
          "name": "anotherAttrForTask2",
          "_links": {
            "self": {
              "href": "http://localhost:8080/attributes/3{?projection}",
              "templated": true
            }
          }
        },
        ...
      ]
    }
  ],
  "_links": {
    "self": {
      "href": "http://localhost:8080/models/1"
    },
    "model": {
      "href": "http://localhost:8080/models/1{?projection}",
      "templated": true
    }
  }
}

关于java - 使用 Spring Data REST 处理子实体集合中的资源链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33352437/

相关文章:

java - 引发扫描查询问题

java - 如何从给定的对串中对具有共同边的节点进行分组?

spring - 在 Spring Rest Data (HATEOAS) 中使用 RestTemplate 创建对象之间的关联

rest - HATEOAS:简洁的描述

java - ww SimpleDateFormat 的奇怪行为

java - spring 应用程序如何在 war 之外保留属性文件

java - 使用 Spring HATEOAS 构建模板化搜索资源 uri

java - 在反序列化期间应用简单的字符串到字符串转换

json - 标题或实体中的 Hateoas 链接

java - Spring Boot 的 Spring Security 默认凭据是什么?