hypersistence-utils icon indicating copy to clipboard operation
hypersistence-utils copied to clipboard

Dirty Checking on Map for HStore Type

Open robshep opened this issue 3 years ago • 9 comments

Hi,

I have an HStore typed Map<String,String> for hibernate5 module.

if I only update the content of the map, the entity is not dirty tracked and appears clean, and is not persisted on update.

I've tried to add this scenario to the test case for this hibernate v5 module but I can't get the repo to compile as-is (mvn toolchains and other dependencies not found)

The hashCode and equals on my entity works fine - I have tests that demonstrates hashcode changes upon map content changes and equals also breaks when only the map changes.

As a workaround, I have the following Entity property

@Entity
public class MyEntity {
   ...
   private int version;
   // getters and setters omitted.  Note this does not use the @Version annotation.
}

Then (As I'm doing my own audit logging, I know when a change has occurred I can do the following:

if ( mergeUpdated ) {
    myEntity.setVersion(myEntity.getMapProp().hashcode());
}

This statement makes the entity itself dirty and is persisted as expected.

I don't know if this is something that can be adjusted by this library? I've no idea how much of hibernate's instrumentation is in the Map implementation it provides after loading an entity and whether or not that can be forced to check a different way.

Thanks

Rob

robshep avatar Jun 26 '22 20:06 robshep

You will have to create a replicating test case that demonstrates the issue. Use the existing tests as a reference for the one you'll have to create.

vladmihalcea avatar Jun 27 '22 04:06 vladmihalcea

Thank you.

I did try but the pom references toolchain configuration that I’m not familiar enough with to replicate.

Do you have any pointers for setting up maven to build the codebase?

thanks again

robshep avatar Jun 27 '22 07:06 robshep

Sure thing. Check out this article.

vladmihalcea avatar Jun 27 '22 08:06 vladmihalcea

Hopefully this fork/branch will work. It currently has a failing test - expanding the original hstore test.

https://github.com/robshep/hibernate-types/tree/issue_%23448

robshep avatar Jun 27 '22 13:06 robshep

We can see the suggestion of forcing dirty on the Book with the following alternative patch:

        doInJPA(new JPATransactionFunction<Void>() {
            @Override
            public Void apply(EntityManager entityManager) {
                Book book = entityManager.unwrap(Session.class)
                        .bySimpleNaturalId(Book.class)
                        .load("978-9730228236");

                book.getProperties().put("price", "$144.95");

                entityManager.persist(book);

                return null;
            }
        });

        doInJPA(new JPATransactionFunction<Void>() {
            @Override
            public Void apply(EntityManager entityManager) {
                Book book = entityManager.unwrap(Session.class)
                        .bySimpleNaturalId(Book.class)
                        .load("978-9730228236");

                assertEquals("$44.95", book.getProperties().get("price")); // Uh-Oh!

                return null;
            }
        });

        doInJPA(new JPATransactionFunction<Void>() {
            @Override
            public Void apply(EntityManager entityManager) {
                Book book = entityManager.unwrap(Session.class)
                        .bySimpleNaturalId(Book.class)
                        .load("978-9730228236");

                Map copyProperties = new HashMap();
                copyProperties.putAll(book.getProperties());
                copyProperties.put("price", "$144.95");
                book.setProperties(copyProperties);

                entityManager.persist(book);

                return null;
            }
        });

        doInJPA(new JPATransactionFunction<Void>() {
            @Override
            public Void apply(EntityManager entityManager) {
                Book book = entityManager.unwrap(Session.class)
                        .bySimpleNaturalId(Book.class)
                        .load("978-9730228236");

                assertEquals("$144.95", book.getProperties().get("price")); // OK

                return null;
            }
        });

robshep avatar Jun 27 '22 13:06 robshep

If you managed to replicate it, then you can debug it and see why it fails.

Once you identify the problem, send me a Pull Request with the fix proposal to investigate it.

vladmihalcea avatar Jun 27 '22 16:06 vladmihalcea

Sadly I've reached the limit of my knowledge in hibernate internals. I haven't used it in a long time and it seems the use of interface proxies has disappeared in favour of presumably bytecode instrumenting or something else that doesn't show up in my debugger. I can't see how the standard entity dirty tracking is effected, and so can't propose where to even start resolving this.

I don't expect you to fix it, but sadly I'm unable to sorry.

Feel free to close this. My workaround is inelegant but gets me over the line at present. Thanks for the library in any case.

Rob

robshep avatar Jun 27 '22 18:06 robshep

No worries. In the meanwhile, you could switch to a jsonb column and persist the Map attribute to it.

vladmihalcea avatar Jun 27 '22 19:06 vladmihalcea

FYI I saw exactly the same happen for a JSONB column, so this might not be a problem in this project but rather in Hibernate itself.

rhuitl avatar Jun 09 '23 20:06 rhuitl