【问题标题】:Spring Data Rest - can not update (PATCH) a list of child entities that have a reference to another entitySpring Data Rest - 无法更新(PATCH)引用另一个实体的子实体列表
【发布时间】:2017-05-10 11:53:26
【问题描述】:

我有三个实体:Parent、它的 Child 和一些 Reference:

家长

@Entity
@Table(name = "parents")
public class Parent extends LongId {

    @NonNull
    @Column(nullable = false)
    private String name = "Undefine";

    @NonNull
    @OneToMany(cascade = MERGE)
    private List<Child> children = new ArrayList<>();
}

儿童

@Entity
@Table(name = "children")
public class Child extends LongId {

    @NonNull
    @Column(nullable = false)
    private String name;

    @NonNull
    @ManyToOne(optional = false)
    private Reference reference;
}

参考

@Entity
@Table(name = "references")
public class Reference extends LongId {

    @NotEmpty
    @Column(nullable = false)
    @Length(min = 3)
    @NonNull
    private String description;
}

还有他们的仓库:

@RepositoryRestResource
public interface ParentRepo extends JpaRepository<Parent, Long> {
}

@RepositoryRestResource
public interface ChildRepo extends JpaRepository<Child, Long> {
}

@RepositoryRestResource
public interface ReferenceRepo extends JpaRepository<Reference, Long> {
}

事先我坚持了几个孩子的参考。然后我创建了一个有一个孩子的新父母:

POST http://localhost:8080/api/parents
{
    "name" : "parent2",
    "children" : [
        "http://localhost:8080/api/children/3"
        ]
}

并已成功获得状态 201 Created。 但是当我尝试将另一个孩子添加到 parent2 时(用 PATCH 更新它):

PATCH http://localhost:8080/api/parents/2
{
    "name" : "parent2",
    "children" : [
        "http://localhost:8080/api/children/3",
        "http://localhost:8080/api/children/4"
        ]
}

我有一个错误:

{
  "cause": {
    "cause": null,
    "message": "Can not construct instance of restsdemo.domain.entity.Child: no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/children/4')\n at [Source: N/A; line: -1, column: -1]"
  },
  "message": "Could not read payload!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of restsdemo.domain.entity.Child: no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/children/4')\n at [Source: N/A; line: -1, column: -1]"
}

如果我从 Child 中删除指向 Reference 实体的链接:

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "children")
public class Child extends LongId {

    @NonNull
    @Column(nullable = false)
    private String name;

    // @NonNull
    // @ManyToOne(optional = false)
    // private Reference reference;
}

一切正常 - child4 已成功添加到 parent2。

如果子实体引用其他实体,您能否指出如何正确更新子实体列表?

这个例子的回购在这里:https://github.com/Cepr0/restdemo

【问题讨论】:

  • 尝试在 Parent 类中添加另一个构造函数后使用此签名 public Parent(String name, List&lt;Children&gt; children)
  • 谢谢@abhishek!但这没有帮助(
  • @AbhishekBhatia,我已经有一个构造函数 public Parent(String name, Child... children) 并且添加你的构造函数没有帮助。 (我在每个类中都有构造函数 - 我使用 Lombock)

标签: java spring-data-rest


【解决方案1】:

我不敢相信!!! 我向 Child 添加了一个带有 String 参数的空构造函数,一切正常!

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "children")
public class Child extends LongId {

    @NonNull
    @Column(nullable = false)
    private String name;

    @NonNull
    @ManyToOne(optional = false)
    private Reference reference;

    public Child(String reference) {
    }
}

谁能解释它为什么起作用?!

【讨论】:

  • +1 同样的情况,错误信息也显示no String argument constructor/factory method。上面提到我要添加一个字符串参数构造函数,我解决了这个问题。并且我也不知道它为什么起作用。你找到它的解释了吗?
  • @g1eny0ung - 还没有))
  • 我看起来这是一个已知问题,并且似乎受到了一些关注。这是关于这个问题的Spring Data Rest Jira Ticket。
【解决方案2】:

这些解决方案都不起作用。当然,您可以创建一个接受字符串的假虚拟构造函数,这样错误就会消失,但这没用,似乎什么也没做! (例如,它将以字符串 uri 的形式传递,例如 localhost:8080/address/1,这是无用的,因为没有将其映射到域实体的解析。

根据我的研究,没有办法让它发挥作用。一旦您实现了自定义控制器,您就不能再自动将 URI 从 @RequestBody 对象转换为存储库托管域实体。基本上,弹簧数据休息添加了秘密调味料来解决在您定义的手动控制器中无法访问的 URI。

您可能可以实现一些方法来创建 URI 解析器,但祝您好运,因为它很可能没有记录。

作为一种解决方法或“hack”。尝试只使用 spring 数据休息存储库端点,甚至可以使用诸如 @HandleBeforeSave 之类的存储库事件挂钩,或者如果这不是我考虑过的选项。

这不起作用,因为 URI 不会映射到实体

   {
   "name": "joe",
   "addresses": ["localhost:8080/address/1",
                  "localhost:8080/address/8", 
                  "localhost:8080/address/23"]
    }

而是创建一个 DTO 并传入主键,在自定义控制器中您可以自己解决(例如,通过 AddressRepository 中的主键获取地址)。这是一种相当手动的方法,显然并不理想。

{
   "name": "joe",
   "addresses": [1, 8, 23]
}

【讨论】:

    猜你喜欢
    • 2020-01-10
    • 2018-05-04
    • 2018-05-01
    • 2019-01-07
    • 2019-09-30
    • 1970-01-01
    • 2013-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多