[JIRA] (HHH-16586) When merging a persisted entity with a null Version, Hibernate treats entity as transient instead of throwing
by Matthieu Rickly (JIRA)
Matthieu Rickly ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%... ) *updated* an issue
Hibernate ORM ( https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiZGJjZWIwMzli... ) / Bug ( https://hibernate.atlassian.net/browse/HHH-16586?atlOrigin=eyJpIjoiZGJjZW... ) HHH-16586 ( https://hibernate.atlassian.net/browse/HHH-16586?atlOrigin=eyJpIjoiZGJjZW... ) When merging a persisted entity with a null Version, Hibernate treats entity as transient instead of throwing ( https://hibernate.atlassian.net/browse/HHH-16586?atlOrigin=eyJpIjoiZGJjZW... )
Change By: Matthieu Rickly ( https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=557058%... )
When an * Entity is passed with all null payload fields in has a PUT REST resource, the entity is not marked as dirty, because the fields Version field of the passed entity are compared with the ( type 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 New instance gets persisted. </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} Instance is modified 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 = merge is attempted with a null ;
return result;
}
}
}
{noformat}
PUT REST Resource:
{noformat} @PUT
@Path("/{id}")
@Consumes(MediaType version field. 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 * Hibernate does not complain about missing 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 Instead the test shows that the entity has not been updated is treated as transient, although it exists 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=EmailN... ) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100225- sha1:4a1ccf9 )