Spring Data REST - PUT 请求自 v.2.5.7 以来无法正常工作

Posted

技术标签:

【中文标题】Spring Data REST - PUT 请求自 v.2.5.7 以来无法正常工作【英文标题】:Spring Data REST - PUT request does not work properly since v.2.5.7 【发布时间】:2018-01-19 02:03:22 【问题描述】:

由于版本 2.5.7 Spring Data REST 无法正确执行 PUT 请求来更新具有关联资源的资源。与按预期工作的 PATCH 请求不同!

例如,PersonAddres 具有多对一关联。如果我们使用 SDR v.2.5.6 (Spring Boot v.1.4.3) 执行 PUT 请求,那么一切正常。但是如果我们切换到 2.5.7 版本(即 Spring Boot v.1.4.4),我们会得到一个错误:

无法构造地址的实例:没有字符串参数构造函数/工厂方法可以从字符串值反序列化

其他类型的关联也会发生同样的情况,例如一对多(单向和双向) - 请参阅我的 example application 代码和测试。

自 1.4.4 以来的 所有 版本的 Spring Boot 中都存在此问题,包括最新的稳定 1.5.6 版本以及最新的 2.0.0-SNAPSHOT 版本!

要解决这种情况,我们只需切换到 SDR v.2.5.6 (Spring Boot v.1.4.3)。

我已经准备了 Postman 请求集合来帮助您解决这个问题:SDR PUT Issue

2017 年 8 月 14 日更新

我找到了如何避免错误Can not construct instance of Address: no String-argument constructor/factory method to deserialize from String value

由于我在这个项目中使用Lombok, 只需告诉 Lombok 使用 @ConstructorProperties 注释在 generated constructors。 所以我在“lombok.config”文件中设置了lombok.anyConstructor.suppressConstructorProperties=true,错误就消失了。

不幸的是,发现了一个新问题 - PUT 请求根本不会更新关联的对象

下面的例子证明了这一点。当我们试图通过将他的地址从addresses/1(初始值)更改为addresses/2 来更新Person 时,它保持不变:addresses/1!与之前的问题一样,这个问题存在于自 1.4.4 以来的 所有 版本的 Spring Boot(SDR - 从 v.2.5.7 开始)。

我调试了我的项目,发现问题的原因隐藏在方法 DomainObjectReader#mergeForPut(请参阅 its source)中 - 它从不用新资源替换相关资源。

在我在Spring JIRA 上发布此问题之前,请在此处报告您是否在项目中遇到此问题以及您对此有何看法

您可以获取我的测试 here 并在您的项目中进行检查 - 该测试是“独立的”并且不依赖于其他类/模块(我希望仅排除 H2)。

@Entity
public class Person 

    private String name;

    @ManyToOne
    private Address address;

    // other stuff


@Entity    
public class Address 

    private String street;

    // other stuff

尝试更新人:

PUT http://localhost:8080/api/persons/1

    "name": "person1u",
    "address": "http://localhost:8080/api/addresses/2"

得到正确的响应:


    "name": "person1u",
    "_links": 
        "self": 
            "href": "http://localhost:8080/api/persons/1"
        ,
        "person": 
            "href": "http://localhost:8080/api/persons/1"
        ,
        "address": 
            "href": "http://localhost:8080/api/persons/1/address"
        
    

然后检查人员的“新”地址 - 地址未更新:

GET http://localhost:8080/api/persons/1/address

    "street": "address1",
    "_links": 
        "self": 
            "href": "http://localhost:8080/api/addresses/1"
        ,
        "address": 
            "href": "http://localhost:8080/api/addresses/1"
        
    

2017 年 8 月 24 日更新

感谢 Scott C.answer,事实证明 SDR 有一个错误,在两张票中进行了描述:DATAREST-1001 和 DATAREST-1012。

【问题讨论】:

链接http://localhost:8080/api/persons/1/address (v.2.5.6) 的对象是什么? @AndrewTobilko 地址1: "street": "address1", "_links": "self": "href": "http://localhost:8080/api/addresses/1" , "address": "href": "http://localhost:8080/api/addresses/1" 我不明白为什么它适用于第一个版本。应该抛出相同的异常,因为Address 实例不能从单个String 构造。 BaseEntity 是什么? @AndrewTobilko 这是实体的base class。所有 ctor 都在场 - 我在项目中使用 Lombok... @halfer 好的。谢谢。 【参考方案1】:

看起来问题有already been reported as a bug: - 请验证。据我所知,这是您在上面报告的问题。

注意,我正在将我之前的答案修改为这个错误报告。

【讨论】:

感谢您的回复!我知道 PUT 和 PATCH 之间的区别。你绝对正确 - PUT 必须用新实体完全替换实体,包括简单字段和对其他对象的引用。正如RFC2616 所说:封闭实体应该被视为驻留在源服务器上的实体的修改版本。如您所见,Person 对象的字段 'name' 被新的 'person1u' 值替换,但 address 引用 - 不是。所以我认为 - 这确实是从 v. 1.4.4 出现在 SB 中的一个错误 我不确定这是否与此处相同。是的,它可能是相关的,但是您链接的错误仅与集合有关,而此错误甚至会影响非集合资源关联(例如 @ManyToOne 关系) 我链接该票证的原因是因为它似乎是链接到同一代码区域的其他人的票证。如果您查看引用的票证中的 cmets,您也会看到这张票证 jira.spring.io/plugins/servlet/mobile#issue/DATAREST-1001,这更直接相关。也就是说,我认为上述票证是解决此问题需要解决的问题。 谢谢!您更新的答案正在解决这个问题 - SDR 在 PUT 处理中有一个错误(奇怪的是我自己没有找到这些票)。 为了将来参考以及我如何找到错误报告,我去 Jira 搜索了 Put,它返回了大约 70 个问题。我阅读了几张票的摘要。当我找到一张候选票时,我阅读了整张票以与您的问题进行比较。了解代码领域会有所帮助,因此在 OP 中进行的研究很有帮助。毫无疑问,找到它需要时间。【参考方案2】:

我同意你的看法,这是 Spring Data REST 中的一个错误,应该报告。

我在我的项目中遇到了同样的问题,通过 PATCH 请求更新实体可以正常工作,但 PUT 请求仅更新给定实体的字段,但不更新其相关资源。

为什么我认为这是一个错误?

正如人们正确指出的那样,PUT 应该用于用修改后的版本替换整个资源,这表明它应该可以工作,如果您提供资源的所有字段 (就像在 POST 请求中一样)。然而,在当前的 Spring Data REST 版本中,只有实体的简单字段被更新,相关资源保持不变,这使得 PUT 请求只能“部分工作”,这绝对不是预期的行为(即使它满足 RFC) . 此外,Spring Data REST 甚至允许您执行部分 PUT 请求,即通过 PUT 仅更新您的姓名字段。但它不适用于该地址。 这意味着 Spring Data REST 不能完全按照 RFC 指定的方式工作(这可能是另一场争论),但它也没有提供一致的用法 - 当更新一个字段有效而更新其他字段时,没有任何错误迹象。

为了记录,我使用的是 Spring Data REST 2.6.3。

【讨论】:

感谢您的意见!我已经开始怀疑这个问题的客观性了...)) presentation Olver Gierke 根本不使用 PUT 来更新/替换资源。也许我们什么都不知道?...)) 好吧,来自link Scott C. 的帖子实际上看起来 PUT 在 PATCH 被引入后并不是很有用。唯一合理的用例似乎是您要求请求是幂等的。所以也许 PATCH 应该优先用于 common 更新。但它并没有改变它是一个错误的事实,因为 PUT 没有按预期工作。 您能否将问题发布到 Spring Data REST JIRA 后链接? 当然,我要把它贴在这里。

以上是关于Spring Data REST - PUT 请求自 v.2.5.7 以来无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

Spring Rest Controller PUT 方法请求正文验证?

如何在 Spring Data Rest 中的两个实体之间发布和 PUT 关系@OneToMany / @ManyToOne?

Spring REST

Spring REST

spring-data-rest 集成测试因简单的 json 请求而失败

Spring Data REST PATCH请求 远程代码执行漏洞案例(CVE-2017-8046)