使用 Spring Data REST,为什么 @Version 属性 变成了 ETag 而没有包含在表示中?
With Spring Data REST, why is the @Version property becoming an ETag and not included in the representation?
在 Spring 数据 REST 中(通过 Spring Boot 1.3.3),当我 GET
资源 collection 时,例如 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 数据存储库时:
@Configuration
public class ApplicationRepositoryConfiguration
extends RepositoryRestMvcConfiguration
{
@Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config
)
{
config.exposeIdsFor(Person.class);
config.setBasePath("/api/");
}
}
@Version
是更新时递增的数据行的版本,并包含在 ETag
HTTP Header查询特定资源时的数据。不必在 collection 中的每个资源上调用 GET
,我更愿意在 collection GET
中获取 @Version
,这样我就可以编写我的应用程序检查每个资源更新 的 @Version
值,而无需 执行 n
添加 GET
round-trips.
有没有办法在每个资源中包含 @Version
字段 collection 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;
…
}
不,没有。 ETag 是 HTTP 等同于在后端表示为 @Value
属性 的内容。 Spring Data REST 将所有在 HTTP 协议中具有相应机制的后端相关属性完全转换为:ids 成为 URI(并且也不应成为有效负载的一部分),@LastModifiedDate
属性成为 headers,@Version
属性,成为ETags。
原因很简单:如果您使用 HTTP,请使用您可用的协议来实现在数据访问级别上实现的内容。这是 Spring Data REST 的一个方面 而不是 只是将数据库公开到网络,而是实际检查您的模型并将模型特征转换为协议特定的方式。
长话短说:使用 Spring Data REST,您有两个更新选项:
- Just
PUT
without an If-Match
header — 在聚合加载时强制覆盖服务器上存在的任何内容,传入数据映射到它并写回。如果另一个客户端同时更改了聚合,您仍然会应用乐观锁定(尽管公认非常短 window)。如果是这种情况,您会看到 409 Conflict
.
PUT
与 If-Match
header - Spring 数据 REST 检查提交的 ETag 与聚合版本 属性 的当前值和return a 412 Precondition Failed
以防此时出现不匹配。在这种情况下,客户端可以查找资源的当前状态并决定如何进行。他们可能只是决定使用 PUT
而不使用 If-Match
header. 覆盖服务器上的内容
可以对 GET 请求进行类似的优化:
GET
with If-None-Match
(ETag) / If-Modified-Since
(with Last-Modified header value) - 你会看到一个 304 Not Modified
以防资源仍处于与以前相同的状态,因此您可以避免为响应花费带宽。
- 普通
GET
将始终return表示。
我认为不在正文中包含版本是一个错误,因为我们经常拉取资源页面,所以我们不能依赖 ETag。至少它应该是可配置的,但目前不是。
我简单的给暴露版本的实体添加了另一个函数,我在Spring Boot 2.4.4:
// ...
@Version
private Long version;
public Long getCurrentVersion() {
return version;
}
// ...
在 Spring 数据 REST 中(通过 Spring Boot 1.3.3),当我 GET
资源 collection 时,例如 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 数据存储库时:
@Configuration
public class ApplicationRepositoryConfiguration
extends RepositoryRestMvcConfiguration
{
@Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config
)
{
config.exposeIdsFor(Person.class);
config.setBasePath("/api/");
}
}
@Version
是更新时递增的数据行的版本,并包含在 ETag
HTTP Header查询特定资源时的数据。不必在 collection 中的每个资源上调用 GET
,我更愿意在 collection GET
中获取 @Version
,这样我就可以编写我的应用程序检查每个资源更新 的 @Version
值,而无需 执行 n
添加 GET
round-trips.
有没有办法在每个资源中包含 @Version
字段 collection 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;
…
}
不,没有。 ETag 是 HTTP 等同于在后端表示为 @Value
属性 的内容。 Spring Data REST 将所有在 HTTP 协议中具有相应机制的后端相关属性完全转换为:ids 成为 URI(并且也不应成为有效负载的一部分),@LastModifiedDate
属性成为 headers,@Version
属性,成为ETags。
原因很简单:如果您使用 HTTP,请使用您可用的协议来实现在数据访问级别上实现的内容。这是 Spring Data REST 的一个方面 而不是 只是将数据库公开到网络,而是实际检查您的模型并将模型特征转换为协议特定的方式。
长话短说:使用 Spring Data REST,您有两个更新选项:
- Just
PUT
without anIf-Match
header — 在聚合加载时强制覆盖服务器上存在的任何内容,传入数据映射到它并写回。如果另一个客户端同时更改了聚合,您仍然会应用乐观锁定(尽管公认非常短 window)。如果是这种情况,您会看到409 Conflict
. PUT
与If-Match
header - Spring 数据 REST 检查提交的 ETag 与聚合版本 属性 的当前值和return a412 Precondition Failed
以防此时出现不匹配。在这种情况下,客户端可以查找资源的当前状态并决定如何进行。他们可能只是决定使用PUT
而不使用If-Match
header. 覆盖服务器上的内容
可以对 GET 请求进行类似的优化:
GET
withIf-None-Match
(ETag) /If-Modified-Since
(with Last-Modified header value) - 你会看到一个304 Not Modified
以防资源仍处于与以前相同的状态,因此您可以避免为响应花费带宽。- 普通
GET
将始终return表示。
我认为不在正文中包含版本是一个错误,因为我们经常拉取资源页面,所以我们不能依赖 ETag。至少它应该是可配置的,但目前不是。
我简单的给暴露版本的实体添加了另一个函数,我在Spring Boot 2.4.4:
// ...
@Version
private Long version;
public Long getCurrentVersion() {
return version;
}
// ...