Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DbJsonB immediately dirty on load if multiple enum as property #3129

Open
AntoineDuComptoirDesPharmacies opened this issue Jul 4, 2023 · 1 comment

Comments

@AntoineDuComptoirDesPharmacies

Hi everyone,

We are having a strange behavior with DbJsonB feature on POJO where multiple properties are Enum.
Ebean Version : 13.20.1

Expected behavior

Having an entity A with a DbJsonB property composed of a POJO B with multiple fields (each fields is an enum).
After DB/find.byId of entity A , DB.beanState should answer that the entity is not dirty.

Actual behavior

Having an entity A with a DbJsonB property composed of a POJO B with multiple fields (each fields is an enum).
After DB/find.byId of entity A , DB.beanState answer that the entity is dirty on the DbJsonB property even without change.

Steps to reproduce

@Entity
public class A{
    [...]
    @DbJsonB
    @Column(nullable = false)
    private A.B sources;
    [...]

    @JsonInclude(JsonInclude.Include.NON_NULL) // Do not encode null to "null" to gain space in DB
    static public class B {
        private DataProviderEnum field1;
        private DataProviderEnum field2;

        public B() {
           // NOTHING TO DO
        }

        @JsonCreator
        public B(
                @JsonProperty("field1") DataProviderEnum field1,
                @JsonProperty("field2") DataProviderEnum field2
                                   ) {
            this.field1= field1;
            this.field2 = field2;
        }


        public DataProviderEnum getField1() {
            return field1;
        }

        public void setField1(DataProviderEnum field1) {
            this.field1= field1;
        }

        public DataProviderEnum getField2() {
            return field2;
        }

        public void setField2(DataProviderEnum field2) {
            this.field2= field2;
        }

        @Override
        public String toString() {
            return "ProductFieldSources{"  
                    "field1="   field1 
                    ", field2="   field2 
                    '}';
        }
    }
}

public enum DataProviderEnum {
    @EnumValue("VALUE1")
    VALUE1,
    @EnumValue("VALUE2")
    VALUE2,
    @EnumValue("VALUE3")
    VALUE3
}

Here is the value for this property in database :

{"field1": "VALUE1", "field2": "VALUE2"}

When executing :
DB.beanState(A.find.byId(1L)).dirtyValues()
If mutationDetection = SOURCE :
=> Return the property sources with Old being null and New being the value from database.

If mutationDetection = DEFAULT :
=> Return the property sources with Old and New value being the same (value from database) but with different object reference.

Important note :

  • If I replace DataProviderEnum by String type, the problem disappear
  • If I delete one of the field from database, (eg. : {"field1": "VALUE1"}), then the problem disappear
  • Even if the second field have no meaning with the schema, the problem stay (eg. : {"field1": "VALUE1", "any": "blue"})

Thanks in advance for your help.
Yours faithfully,
LCDP

@AntoineDuComptoirDesPharmacies
Copy link
Author

AntoineDuComptoirDesPharmacies commented May 2, 2024

We did further analysis on this problem and we figure out the exact cause of this wrong dirtiness detection.

The problem come from PostgreSQL not respecting order of fields while using JSONB column type because the data is stored optimized.
See : https://www.postgresql.org/docs/10/datatype-json.html

It seems that PostgreSQL is storing the keys as following :

  • At first, order keys by their length
  • If same length, then alphabetically

In class ScalarJsonJacksonMapper.java, call to class RsetDataReader -> reader.getString(); this get the raw BD value and so return a JSON which have a fields ordering done by PostgreSQL.
When values are compared in isEqualToObject of SourceMutableValue and ChecksumMutableValue, it will not match and be considered dirty at the value of obj is serialized using Jackson, resulting in different fields ordering.

This dirtiness will never be resolved and these bean will always be considered dirty on load because when Ebean will persist the new value (with its own ordering) in the database, PostgreSQL will reorder the fields to optimize the way it is stored.

We successfully avoid these wrong dirtiness status adding JsonPropertyOrder annotation on the class using complete and explicit field ordering following the exact order as PostgreSQL but it is near to impossible to maintain when adding/removing fields.

We were wondering if it could be interesting to modify the method isEqualToObject in a way that both side will have their fields ordered before comparing. Or maybe we could reorder the result of RsetDataReader according to the order of the JsonB POJO. Or maybe you will come up with a better solution ?

Maybe you will have an idea @rbygrave or @rPraml ?

Thanks in advance for your help.
Yours faithfully,
LCDP

AntoineDuComptoirDesPharmacies added a commit to LeComptoirDesPharmacies/ebean that referenced this issue May 14, 2024
…ld ordering on both sides (due to PostgreSQL reordering fields for JsonB types)
AntoineDuComptoirDesPharmacies added a commit to LeComptoirDesPharmacies/ebean that referenced this issue May 16, 2024
Fix to verify DBJson dirtiness using same field ordering on both sides (due to PostgreSQL reordering fields for JsonB types)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant