Issue
I use Quarkus + Hibernate to sync data to the DB and I've noticed during testing that sometimes my entity isn't updated. I've created a minimal example adjusting the original example https://github.com/quarkusio/quarkus-quickstarts/tree/main/hibernate-orm-quickstart
Here are my adjustments:
import.sql
DROP TABLE IF EXISTS fruit CASCADE;
CREATE TABLE fruit (
fruitsSequence INT PRIMARY KEY,
name TEXT NOT NULL,
test INT
);
Fruit.java
package org.acme.hibernate.orm;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.google.common.base.Objects;
@Entity
@Table(name = "known_fruits")
public class Fruit {
@Id
@SequenceGenerator(name = "fruitsSequence", sequenceName = "known_fruits_id_seq", allocationSize = 1, initialValue = 10)
@GeneratedValue(generator = "fruitsSequence")
private Integer id;
@Transient
private String name = "";
@Column(name = "test")
private Integer test = -1;
public Fruit() {
}
public Fruit(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name = "name")
@Access(AccessType.PROPERTY)
public String getChangedName() {
return "a" + name;
}
public String getName() {
return name;
}
public void setTest(Integer test) {
this.test = test;
}
public Integer getTest() {
return test;
}
@Column(name = "name")
@Access(AccessType.PROPERTY)
protected void setChangedName(String name) {
this.name = name.substring(1);
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hashCode(name, test);
}
@Override
public boolean equals(Object o) {
if (o instanceof Fruit) {
Fruit other = (Fruit) o;
return name.equals(other.name) && test.equals(other.test);
}
return false;
}
}
Replaced the test withDBTest.java
package org.acme.hibernate.orm;
import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.assertEquals;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.UserTransaction;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class DBTest {
@Inject
EntityManager m_em;
@Inject
UserTransaction m_transaction;
@Test
void testUpdate() throws Exception {
Fruit fruit = given().when().body("{\"name\" : \"Pear\"}").contentType("application/json").post("/fruits")
.then().statusCode(201).extract().as(Fruit.class);
m_transaction.begin();
Fruit db = m_em.find(Fruit.class, fruit.getId());
db.setName("Apple");
db.setTest(13);
m_transaction.commit();
db = m_em.find(Fruit.class, fruit.getId());
assertEquals(13, db.getTest(), "Unexpected test");
assertEquals("Apple", db.getName(), "Unexpected name");
}
@Test
void testUpdateLongName() throws Exception {
Fruit fruit = given().when().body(
"{\"name\" : \"PeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaDBaaaaaaaaaaaaaar\"}")
.contentType("application/json").post("/fruits").then().statusCode(201).extract().as(Fruit.class);
m_transaction.begin();
Fruit db = m_em.find(Fruit.class, fruit.getId());
db.setName(fruit.getName() + "Apple");
db.setTest(13);
m_transaction.commit();
db = m_em.find(Fruit.class, fruit.getId());
assertEquals(13, db.getTest(), "Unexpected test");
assertEquals(fruit.getName() + "Apple", db.getName(), "Unexpected name");
}
@Test
void testUpdateNameOnly() throws Exception {
Fruit fruit = given().when().body("{\"name\" : \"Pear\"}").contentType("application/json").post("/fruits")
.then().statusCode(201).extract().as(Fruit.class);
m_transaction.begin();
Fruit db = m_em.find(Fruit.class, fruit.getId());
db.setName("Apple");
m_transaction.commit();
db = m_em.find(Fruit.class, fruit.getId());
assertEquals(-1, db.getTest(), "Unexpected test");
assertEquals("Apple", db.getName(), "Unexpected name");
}
@Test
void testUpdateNameOnlyREST() throws Exception {
Fruit fruit = given().when().body("{\"name\" : \"Pear\"}").contentType("application/json").post("/fruits")
.then().statusCode(201).extract().as(Fruit.class);
given().when().body("{\"name\" : \"Apple\"}").contentType("application/json").put("/fruits/" + fruit.getId())
.then().statusCode(200).extract().as(Fruit.class);
Fruit db = m_em.find(Fruit.class, fruit.getId());
assertEquals(-1, db.getTest(), "Unexpected test");
assertEquals("Apple", db.getName(), "Unexpected name");
}
}
What I see is that testUpdateNameOnly
and testUpdateNameOnlyREST
fail whereas the other tests run as expected. In my original testcase, even changing another field didn't update the TEXT field hence the test with the long name. The reason why the name is altered when writing it to the DB is to encrypt it (the outcome is a BASE64 string).
I am not sure if this is a configuration issue or an actual bug.
Thanks for helps in advance!
Solution
As suggested by the comments this seems to be a bug (https://github.com/quarkusio/quarkus/issues/16619). It works with the AttributeConver:
Fruit.java:
package org.acme.hibernate.orm;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import com.google.common.base.Objects;
@Entity
@Table(name = "known_fruits")
public class Fruit {
@Id
@SequenceGenerator(name = "fruitsSequence", sequenceName = "known_fruits_id_seq", allocationSize = 1, initialValue = 10)
@GeneratedValue(generator = "fruitsSequence")
private Integer id;
@Column(name = "name")
@Convert(converter = NameConverter.class)
private String name = "";
@Column(name = "test")
private Integer test = -1;
public Fruit() {
}
public Fruit(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setTest(Integer test) {
this.test = test;
}
public Integer getTest() {
return test;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hashCode(name, test);
}
@Override
public boolean equals(Object o) {
if (o instanceof Fruit) {
Fruit other = (Fruit) o;
return name.equals(other.name) && test.equals(other.test);
}
return false;
}
}
NameConverter.java:
package org.acme.hibernate.orm;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter
public class NameConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String name) {
return "a" + name;
}
@Override
public String convertToEntityAttribute(String name) {
return name.substring(1);
}
}
Answered By - MoH