Canonical _links with Spring HATEOAS

时间:2014-07-04 08:51:07

标签: spring spring-data-jpa spring-data-rest spring-hateoas

我们正在构建一个类似于spring.io指南“Accessing JPA Data with REST”的RESTful Web服务。要重现下面的示例输出,只需将 ManyToOne -Relation添加到 Person ,如下所示:

// ...

@Entity
public class Person {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  private String firstName;
  private String lastName;

  @ManyToOne
  private Person father;

  // getters and setters
}

添加一些样本数据后的GET请求产生:

{
  "firstName" : "Paul",
  "lastName" : "Mustermann",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    },
    "father" : {
      "href" : "http://localhost:8080/people/1/father"
    }
  }
}

但是,鉴于保罗的父亲存有ID 2,我们期望的结果将是其关系的规范网址:

// ...
    "father" : {
      "href" : "http://localhost:8080/people/2"
    }
// ...

如果某些人为空,这当然会导致问题(好的,这在这里没有多大意义......;)),但在这种情况下我们不想渲染JSON中的链接。

我们已经尝试实现 ResourceProcessor 来实现这一目标,但似乎在调用处理器时尚未填充链接。我们设法添加指向所需规范网址的其他链接,但未能修改以后添加的链接。

问题:是否有通用方法来自定义所有资源的链接生成?

澄清我们对规范URL的需求:我们使用SproutCore Javascript框架来访问RESTful Web服务。它使用了类似“ORM”的数据源抽象,我们已经实现了Spring生成的JSON输出的通用处理程序。当查询所有人时,我们需要向具有q关系的n个人发送n *(1 + q)个请求(而不仅仅是n个)给其他人,以将它们同步到客户端数据源。这是因为默认的“非规范”链接绝对不包含有关正在设置的父亲或父亲的id的信息。似乎这会导致大量不必要的请求,如果初始响应首先包含更多信息,则可以轻松避免这些请求。

另一个解决方案是将父亲的id添加到关系中,例如:

"father" : {
  "href" : "http://localhost:8080/people/1/father",
  "id" : 2
}

3 个答案:

答案 0 :(得分:2)

有一个讨论,Spring Data Rest团队解释了为什么属性以这种方式呈现为链接。说过你可以通过抑制SDR生成的链接并实现ResourceProcessor来实现你喜欢的功能。所以你的Person类看起来如下所示。请注意注释@RestResource(exported = false)以禁止链接

@Entity
public class Person {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  private String firstName;
  private String lastName;

  @ManyToOne
  @RestResource(exported = false)
  private Person father;

  // getters and setters
}

您的ResourceProcessor类看起来像

public class EmployeeResourceProcessor implements
        ResourceProcessor<Resource<Person>> {

    @Autowired
    private EntityLinks entityLinks;

    @Override
    public Resource<Person> process(Resource<Person> resource) {
        Person person = resource.getContent();
        if (person.getFather() != null) {
            resource.add(entityLinks.linkForSingleResour(Person.class, person.getFather().getId())
                .withRel("father"));
        }
        return resource;
    }

}

上述解决方案仅在father值与Person一起急切获取时才有效。否则,您需要拥有属性fatherId并使用它而不是father属性。不要忘记使用杰克逊@ignore...来隐藏fatherId以回应JSON。

注意:我自己没有测试过,但猜测它会起作用

答案 1 :(得分:1)

由于我遇到同样的问题,我在spring-data-rest创建了一个Jira问题: https://jira.spring.io/browse/DATAREST-682

如果有足够的人投票支持它,也许我们可以说服一些开发者实施它: - )。

答案 2 :(得分:0)

你正在努力展示规范链接,这很奇怪。一旦检索到/ father的资源,自我链接应该是规范的...但是真的没有理由强迫父亲关系成为规范...也许是一些缓存方案?

针对您的具体问题......您依赖自动生成的控制器,因此您放弃了对许多链接做出决策的权利。如果您拥有自己的PersonController,那么您将更多地负责链接结构。如果您创建了自己的控制器,则可以将EntityLinks https://github.com/spring-projects/spring-hateoas#entitylinks与父亲的ID一起使用.IE

@Controller
@ExposesResourceFor(Person.class)
@RequestMapping("/people")
class PersonController {

  @RequestMapping
  ResponseEntity people(…) { … }

  @RequestMapping("/{id}")
  ResponseEntity person(@PathVariable("id") … ) {
    PersonResource p = ....
    if(p.father){
      p.addLink(entityLinks.linkToSingleResource(Person.class, orderId).withRel("father");
    }
  }
}

但是,改变URL

似乎需要付出很多努力