quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.55k stars 2.62k forks source link

Hibernate: Entity not updated when changed value provided via method #16619

Closed morhei closed 1 year ago

morhei commented 3 years ago

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

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;
    }
}

DBTest.java


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've posted the original question on stackoverflow. Using an AttributeConverter works as expected however, during the conversion I'd like to access another field of the Fruit class.

Java version: OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.10+9)

quarkus-bot[bot] commented 3 years ago

/cc @Sanne, @gsmet, @yrodiere

geoand commented 1 year ago

Is this still an issue with Quarkus 3.2.z?

geoand commented 1 year ago

Closing for lack of feedback