In Spring Data REST (via Spring Boot 1.3.3), when I GET
a resource collection of, say, people
, the @Version
property is not included with the resources:
$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
by default, or when I configure my Spring Data repository:
@Configuration
public class ApplicationRepositoryConfiguration
extends RepositoryRestMvcConfiguration
{
@Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config
)
{
config.exposeIdsFor(Person.class);
config.setBasePath("/api/");
}
}
The @Version
is the version of the row of data which is incremented on updates, and included in the ETag
HTTP Header data when I query a specific resource. Instead of having to invoke a GET
on each resource in the collection, I'd prefer getting the @Version
in the collection GET
so I can write my application to check the @Version
value on each resource update without performing the n
addition GET
round-trips.
Is there a way to include the @Version
field in each of the resources a collection GET
?
The entity definition looks like this:
@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;
…
}
From the official Spring documentation on ETag support: An ETag (entity tag) is an HTTP response header returned by an HTTP/1.1 compliant web server used to determine change in content at a given URL. We can use ETags for two things – caching and conditional requests.
Spring Data REST is a framework that builds itself on top of the applications data repositories and expose those repositories in the form of REST endpoints. In order to make it easier for the clients to discover the HTTP access points exposed by the repositories, Spring Data REST uses hypermedia driven endpoints.
Spring Data REST can be used to expose HATEOAS RESTful resources around Spring Data repositories. Without writing a lot of code, we can expose RESTful API around Spring Data Repositories.
The spring-data-rest-webmvc is the project describing the main concepts of spring-data-rest which is the one of the main spring modules.
No, there is not. The ETag is the HTTP equivalent to what's expressed as @Value
property in the backend. Spring Data REST turns all backend related properties that have a corresponding mechanism in the HTTP protocol into exactly those: ids become URIs (and shouldn't be part of the payload either), @LastModifiedDate
properties become headers, @Version
properties, become ETags.
The reason is pretty simple: if you use HTTP, use the protocol means that are available to you to achieve things that are implemented on the data access level. That's one aspect in which Spring Data REST is not simply exposing a database to the web but actually inspects your model and translates model characteristics into protocol specific means.
Long story short: with Spring Data REST you have two options for updates:
PUT
without an If-Match
header — enforces overriding whatever is present on the server as the aggregate gets loaded, incoming data mapped onto it and it written back. You still get optimistic locking applied if another client changed the aggregate in the meantime (although an admittedly very short window). If that's the case you'll see a 409 Conflict
.PUT
with an If-Match
header - Spring Data REST checks the ETag submitted against the current value of the version property of the aggregate and return a 412 Precondition Failed
in case there's a mismatch at that point. In that case clients can lookup the current state of the resource and decide how to proceed. They might just decide to override what's on the server using PUT
without an If-Match
header.Similar optimizations can made for GET requests:
GET
with If-None-Match
(ETag) / If-Modified-Since
(with Last-Modified header value) — You'll see a 304 Not Modified
in case the resource is still in the same state as before and you thus avoid spending bandwidth for the response.GET
will always return the representation.I believe not including the version in the body is a mistake, as we often pull a page of resources, so there we can't rely on the ETag. At least it should be configurable which it is not currently.
I simply add another function to the entity that exposes the version, I am on Spring Boot 2.4.4:
// ...
@Version
private Long version;
public Long getCurrentVersion() {
return version;
}
// ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With