使用 Spring Data REST,为啥 @Version 属性成为 ETag 并且不包含在表示中?

Posted

技术标签:

【中文标题】使用 Spring Data REST,为啥 @Version 属性成为 ETag 并且不包含在表示中?【英文标题】:With Spring Data REST, why is the @Version property becoming an ETag and not included in the representation?使用 Spring Data REST,为什么 @Version 属性成为 ETag 并且不包含在表示中? 【发布时间】:2016-08-19 14:20:57 【问题描述】:

在 Spring Data REST(通过 Spring Boot 1.3.3)中,当我 GET 一个资源集合(例如 people)时,@Version 属性不包含在资源中:

$curl -v http://localhost:8080/api/people/1
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/people/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.42.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< ETag: "0"
< Last-Modified: Tue, 26 Apr 2016 00:08:12 GMT
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 26 Apr 2016 00:12:56 GMT
< 

  "id" : 1,
  "createdDate" : 
    "nano" : 351000000,
    "epochSecond" : 1461629292
  ,
  "lastModifiedDate" : 
    "nano" : 351000000,
    "epochSecond" : 1461629292
  ,
  "firstName" : "Bilbo",
  "lastName" : "Baggins",
  "_links" : 
    "self" : 
      "href" : "http://localhost:8080/api/people/1"
    ,
    "person" : 
      "href" : "http://localhost:8080/api/people/1"
    
  
* Connection #0 to host localhost left intact

默认情况下,或者当我配置我的 Spring Data 存储库时:

@Configuration
public class ApplicationRepositoryConfiguration 
    extends RepositoryRestMvcConfiguration 
    
    @Override
    protected void configureRepositoryRestConfiguration(
        RepositoryRestConfiguration config
        ) 
    
        config.exposeIdsFor(Person.class);
        config.setBasePath("/api/");
    

@Version 是更新时递增的数据行版本,并在我查询特定资源时包含在ETag HTTP 标头 数据中。而不是必须在集合中的每个资源上调用GET,我宁愿在集合GET 中获取@Version,这样我就可以编写我的应用程序来检查每个资源更新时的@Version没有执行n添加GET往返。

有没有办法将@Version 字段包含在集合GET 的每个资源中?

实体定义如下所示:

@Data @Entity @EntityListeners(AuditingEntityListener.class)
public class Person 

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

    @CreatedDate
    @Column(nullable=false)
    private Instant createdDate;

    @LastModifiedDate
    @Column(nullable=false)
    private Instant lastModifiedDate;

    @Version
    @JsonProperty
    private Long version;

    …

【问题讨论】:

【参考方案1】:

不,没有。 ETag 是 HTTP 等价于在后端表示为 @Value 属性的内容。 Spring Data REST 将所有在 HTTP 协议中具有相应机制的后端相关属性完全转换为:id 成为 URI(也不应该成为有效负载的一部分),@LastModifiedDate 属性成为标题,@Version 属性成为ETag。

原因很简单:如果您使用 HTTP,请使用您可用的协议手段来实现在数据访问级别上实现的事情。这是 Spring Data REST 的一个方面不是简单地将数据库暴露给网络,而是实际检查您的模型并将模型特征转换为协议特定的手段。

长话短说:使用 Spring Data REST,您有两个更新选项:

    只有PUT 没有If-Match 标头— 强制在加载聚合、映射到它的传入数据并将其写回时覆盖服务器上存在的任何内容。如果另一个客户端同时更改了聚合,您仍然会应用乐观锁定(尽管窗口非常短)。如果是这种情况,您将看到 409 ConflictPUT 带有 If-Match 标头 - Spring Data REST 根据聚合的版本属性的当前值检查提交的 ETag,并返回 412 Precondition Failed,以防此时出现不匹配。在这种情况下,客户端可以查找资源的当前状态并决定如何继续。他们可能只是决定使用没有If-Match 标头的PUT 覆盖服务器上的内容。

可以对 GET 请求进行类似的优化:

    GETIf-None-Match (ETag) / If-Modified-Since (与 Last-Modified 标头值) — 如果资源仍处于与以前相同的状态,您将看到 304 Not Modified,从而避免将带宽用于响应。 普通的GET 将始终返回表示形式。

【讨论】:

感谢@Oliver Gierke 的优化——我会尝试一下。 spring.io/guides/tutorials/react-and-spring-data-rest 还更详细地探讨了条件 PUT 和 GET。 是的,@gregturn -- 这就是我的问题的来源。 :-) 嗨@Oliver Gierke,我正在使用 1. 正如你所描述的那样为我的 Spring-data-rest Web 应用程序执行 PUT,但总是出现 409 冲突。并且异常是 E11000 重复键错误。我已经发布了我的问题here 谢谢。 我想知道是否可以将 ETags 与上述标头一起用于 PATCH 请求。 Spring Data Rest 会起作用吗?【参考方案2】:

我认为在正文中不包括版本是错误的,因为我们经常拉取一页资源,所以我们不能依赖 ETag。至少它应该是可配置的,它目前不是。

我只是在暴露版本的实体中添加另一个函数,我在 Spring Boot 2.4.4 上:

  // ...

  @Version
  private Long version;

  public Long getCurrentVersion() 
    return version;
  

  // ...

【讨论】:

以上是关于使用 Spring Data REST,为啥 @Version 属性成为 ETag 并且不包含在表示中?的主要内容,如果未能解决你的问题,请参考以下文章

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

spring-data-redis 中的HashOperations为啥有三个参数

使用 spring-data-rest 将资源添加到集合

Spring-Data-Rest中时间的数据类型

为啥 Spring Rest 服务在第一次请求时很慢?

为啥 Postman 找不到我的 Spring REST API?