Matthieu Rickly (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%...
) *updated* an issue
Hibernate ORM (
https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiMzI0YWMxNGIy...
) / Bug (
https://hibernate.atlassian.net/browse/HHH-16586?atlOrigin=eyJpIjoiMzI0YW...
) HHH-16586 (
https://hibernate.atlassian.net/browse/HHH-16586?atlOrigin=eyJpIjoiMzI0YW...
) Entity PUT via REST with all null payload fields is not updated in DB (
https://hibernate.atlassian.net/browse/HHH-16586?atlOrigin=eyJpIjoiMzI0YW...
)
Change By: Matthieu Rickly (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%...
)
When an Entity is passed with all null payload fields in a PUT REST resource, the entity
is not marked as dirty, because the fields of the passed entity are compared with the
(null) fields of a new entity. When the entity is merged into the context, I would expect
the dirty marking to be performed between the passed entity and the entity from the
persistence context (stored in the database), but no such thing occurs. Hence no update is
scheduled for the entity and the database entity is unchanged.
Entity:
{noformat}public class Item implements Serializable {
public static final String TABLE_NAME = "T_ITEM";
public static final String COLUMN_ID = "C_ID";
public static final String COLUMN_UUID = "C_UUID";
public static final String COLUMN_NAME = "C_NAME";
public static final String COLUMN_VERSION = "C_VERSION";
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = COLUMN_ID)
private Long id;
@Column(name = COLUMN_UUID, length = 36)
@Type(type = "org.hibernate.type.UUIDCharType")
@DDLComment(value = "Application Assigned Primary Key", type =
"UUID")
private UUID uuid = UUID.randomUUID();
@Column(name = COLUMN_NAME, length = 10)
@DDLComment("Name of Item")
@Size(max = 10)
private String name;
@Version
@Column(name = COLUMN_VERSION)
@DDLComment("Optimistic Locking field")
private Long version;
protected Item() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public UUID getUuid() {
return uuid;
}
public String getName() {
return name;
}
public Long getVersion() {
return version;
}
/**
* {@inheritDoc}
* <p>Only {@code id} and {@code name} are considered.</p>
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Item that = (Item) o;
return Objects.equals(id, that.id) && Objects.equals(name, that.name);
}
/**
* {@inheritDoc}
* <p>Only {@code id} and {@code name} are considered.</p>
*/
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "Item{id='" + id + '\'' + ", name='" + name
+ '\'' + '}';
}
public static final class Builder {
private Item entity = new Item();
public Builder() {
}
public Builder(final Item item) {
this.entity = item;
}
public Builder name(final String name) {
entity.name = name;
return this;
}
public Item build() {
Item result = this.entity;
this.entity = null;
return result;
}
}
}
{noformat}
PUT REST Resource:
{noformat} @PUT
@Path("/{id}")
@Consumes(MediaType.APPLICATION_JSON)
public Response update(@PathParam("id") Long id, @Parameter(description =
"Item", required = true) Item item) {
Item updated = em.merge(item);
return Response.ok(updated).build();
}{noformat}
Test:
{noformat} @Test
void shouldUpdateItem() {
String url = given(defaultSpec())
.when()
.contentType("application/json")
.body("{\"name\":\"bar\"}")
.post(PATH)
.then().extract().header("Location");
JsonPath jsonPath = given(defaultSpec())
.when()
.contentType("application/json")
.get(PATH + "/" + url.substring(url.lastIndexOf("/") + 1)).then()
.statusCode(OK.getStatusCode()).extract().body().jsonPath();
Long id = jsonPath.getLong("id");
String nameAfterPost = jsonPath.getString("name");
Long version = jsonPath.getLong("version");
assertEquals("bar", nameAfterPost);
String body = "{\"id\":" + id + ", \"uuid\":null,
\"name\":null}";
given(defaultSpec())
.when()
.contentType("application/json")
.body(body)
.put(PATH + "/" + id)
.then()
.statusCode(OK.getStatusCode()).extract().body().asString();
String nameAfterUpdate = given(defaultSpec())
.when()
.contentType("application/json")
.get(PATH + "/" + id)
.then()
.statusCode(OK.getStatusCode()).extract().body().jsonPath().getString("name");
assertEquals("bar", nameAfterUpdate);
}{noformat}
The final assert in the test shows that the entity has not been updated in the database.
(
https://hibernate.atlassian.net/browse/HHH-16586#add-comment?atlOrigin=ey...
) Add Comment (
https://hibernate.atlassian.net/browse/HHH-16586#add-comment?atlOrigin=ey...
)
Get Jira notifications on your phone! Download the Jira Cloud app for Android (
https://play.google.com/store/apps/details?id=com.atlassian.android.jira....
) or iOS (
https://itunes.apple.com/app/apple-store/id1006972087?pt=696495&ct=Em...
) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100225- sha1:84d3b45 )