如何使用Spring Data REST实现多态关联。

3
我正在使用Spring Boot 1.5.7,Spring Data REST,Spring JPA,Hibernate,Spring HATEOAS,Spring Validation和Swagge来构建我的Web应用程序。 该应用程序提供REST端点,将由Angular客户端使用。
介绍
在我的模型中,许多实体都有一个Note列表。用户,票,退款等都有一个List<Note>。 根据我在网上找到的一些文档,这似乎是一个很好的使用@Any的例子。 这样做会很方便,因为我也可以避免连接表(User_note,Ticket_note,Refund_note等)。
让我们看一个模型的例子:
@Entity
public class Note extends AbstractEntity {
    private static final long serialVersionUID = -5062313842902549565L;

    @Lob
    private String text;

     @RestResource(rel = "parent")
     @Any(fetch = FetchType.LAZY, optional = false, metaColumn = @Column(name ="item_type"))
     @AnyMetaDef(idType = "long", metaType = "string", metaValues = {
     @MetaValue(targetEntity = User.class, value = "User"),
     @MetaValue(targetEntity = Ticket.class, value = "Ticket"),
     @MetaValue(targetEntity = Refund.class, value = "Refund") })
     @JoinColumn(name = "item_id")
     private AbstractEntity parent;

如您所见,父类是AbstractEntity,它是每个实体bean的超类。

@Entity
public class User extends AbstractEntity {

     private String name;

     @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
     private List<Note> notes = new ArrayList<>();

这样做,Hibernate会创建一个Note表,其中包括两个额外的列(id和type)来映射相关对象。
到目前为止,一切都像预期的那样。问题在于REST方面。
主要需要执行以下几个操作:
  1. 为特定父实体(用户、票务等)插入注释
  2. 获取特定实体的注释列表
  3. 从特定实体的注释列表中删除注释
Spring Data REST基于您创建的存储库发布端点。我为每个实体创建了一个存储库。 问题 在这一点上会出现问题。例如,如果您想给用户添加一个注释,您期望调用POST /api/v1/users/{id}/notes,但不幸的是根据thisthis,您无法这样做。从文档中看来,似乎只能发布链接。而且,由于没有父级,笔记不存在,我不能先保存笔记,然后使用链接将其连接到用户。无论如何,即使可能,我也不喜欢这个想法:我希望所有内容都在事务内完成。
好的,让我们看看enpoint Note。您将看到一个POST http://localhost:8080/api/v1/notes,似乎非常适合保存Note。如果您尝试像这样持久化笔记,您将会遇到此异常:
{
  "parent": 
    "http://localhost:8080/api/v1/users/1"
  ,
  "text": "string",
  "version": 0
}

和异常:

{
  "timestamp": "2017-10-15T22:37:06.668+0000",
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
  "message": "Il valore del campo <null> deve essere di tipo <null>; è stato invece ricevuto il valore <null> di tipo <null>. Correggere il dato e riprovare.",
  "path": "/api/v1/notes",
  "cause": {
    "exception": "com.fasterxml.jackson.databind.JsonMappingException",
    "message": "Can not construct instance of it.rebus.server.model.AbstractEntity: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: org.apache.catalina.connector.CoyoteInputStream@67a66b7d; line: 7, column: 5] (through reference chain: server.model.auditing.Note[\"parent\"])"
  }
}

这是可以理解的。Spring不了解要创建哪个AbstractEntity的具体类。我希望它能从URI中发现,但似乎不行。
那么我们尝试获取用户的所有笔记。在这种情况下,有一个端点GET http://localhost:8080/api/v1/users/1/notes,但我没有成功,因为该端点返回一个HTTP 405错误(我打开了一个相关问题)。 结论 我想知道如何解决我使用SDR和多态关联时遇到的问题。是否有一种方便的方法来实现对模型中每个bean的笔记进行crud操作?(我想避免为每个bean都有一个特定的控制器)。

我会为/users和/notes使用单独的端点。每个Note将包含它所属的用户ID。在用户上获取只需返回Note的ID即可。您可以使用像HATEOAS这样的框架来导航/渲染User的Note。这是处理URL中没有依赖关系的实体的一种非常基本和干净的方式。正如您提到的,您不想为每个bean使用多个控制器(不确定原因),您仍然可以拥有一个控制器并使用自己的逻辑来创建/操作控制器方法中的bean。 - Zaki Anwar Hamdani
@ZakiAnwarHamdani 谢谢您的回复。我正在寻找一种特定的SDR解决方案,而不是使用控制器,而只是使用存储库(稍后SDR将其转换为控制器/端点)。在这个意义上,我不想创建单独的控制器来处理这个问题。此外,问题涉及到多态关联,因此一个Note可以同时引用我的领域中的Customer、Ticket或另一个实体。 - drenda
1个回答

2

我相信您已经解决了问题,但我最近也遇到了同样的问题。所以我将发布如何解决它的方法,希望能帮助其他人。

我必须在抽象类中指定反序列化策略。我使用了以下代码:

@JsonTypeInfo(use = Id.NAME, property = "@entityType")
 @JsonSubTypes({
        @JsonSubTypes.Type(value = User.class, name = "User"),
        @JsonSubTypes.Type(value = Ticket.class, name = "Ticket"),
        @JsonSubTypes.Type(value = Refund.class, name = "Refund"),
})

当我收到一个POST请求时,在控制器级别上,我会稍微修改接收到的对象节点,根据“http”属性值在父子节点中添加“@entityType”属性。通过这样的小改变,现在Spring应该知道应该创建哪个具体类的实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接