How to get Hibernate to recognize a change made to the object graph WITHOUT updating the whole graph to the database

Viewed 204

I'm not really even sure how to google this, so if it's a commonly asked question, please direct me to the answer.

A general description of the issue is that one or more parent records has a a set of child records of type 'Document'. The object graph is actually deeper and more complicated than that, but those are the relevant bits for my issue.

Generally we update the whole object graph with merge() when the user hits Save. But we have a requirement to save the Document record...AND ONLY the Document record as soon as they hit add or remove on the document. They can do as many add/removes as they want. There is no instant update, so when they want to update the description, that happens on the made 'Save'.

The instant update works fine. The problem is that on a later request, the user hits 'Save', thereby updating the parent and child records and if there was a remove during the process, I'm getting javax.persistence.EntityNotFoundException despite the fact that I DID remove the object from the set on it's parent.

On remove, I did this:

    item.getDocuments().remove(document);
    documentService.deleteDocumentByID(document.getId());

The first line removes it from the parent record, which i thought would notify hibernate not to freak out about my deleting it in the next like when I ran an HQL 'delete' on that ID.

Then, in the Save it's basically just a 'merge()' on the whole object graph.

Is there a way to make Hibernate okay with me adding/deleting JUST THAT DOCUMENT outside the merge()? Note that I do NOT want to save the whole object graph on that document add/remove. Just the document record.

1 Answers

When you use merge, all basic attributes of that object and all associations that have CascadeType.MERGE will be flushed to the database. So in order to change what is flushed, you need to configure this correctly.

If you require different update/flush graphs because you have multiple use cases, you will have to come up with a different solution i.e. maybe introduce a DTO and apply the changes from the DTO to the managed entities instead of using merge.

If you would also like to avoid all the unnecessary select statements for state synchronization, I can recommend that you take a look at what Blaze-Persistence Entity-Views has to offer.

You can create an updatable entity view that will be just about updating the documents collection.

@EntityView(Item.class)
@UpdatableEntityView
public interface ItemUpdateView {
  @IdMapping
  Integer getId();
  @UpdatableMapping
  Set<DocumentView> getDocuments();
}
@EntityView(Document.class)
public interface DocumentView {
  @IdMapping
  Integer getId();
  String getName();
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

ItemUpdateView dto = entityViewManager.find(entityManager, ItemUpdateView.class, id);

And flushing the changes can be done like this:

entityViewManager.save(entityManager, dto);

You will see that it will only flush what really changed without having to do select statements, thaks to its dirty tracking capabilities.

Related