-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 
Author Message
 Post subject: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Tue Jun 01, 2010 11:45 am 
Beginner
Beginner

Joined: Wed Jul 30, 2008 8:43 am
Posts: 32
Hi.

Back to basics, this is a very simple scenario, but I couldn't find the good way to solve it, only using hucks.

I have one object A, which contains 3 collections for B, C, D. Here is the example of mappings for one of them:

Code:
@OneToMany(mappedBy = "contractCharges", fetch = FetchType.LAZY)
@Cascade( { CascadeType.DELETE_ORPHAN})
@Sort(type = SortType.NATURAL)
private SortedSet<ContractExpenseEstimate> expenseEstimates = new TreeSet<ContractExpenseEstimate>();


In the UI I create the object A and collections for B, C and D for it. Then try to save the object A and all its collections:

Code:
session.save(A); (1)

for(B b: A.getB()) {
    session.save(b);
}

for(C c: A.getC()) {
    session.save(c);
}

for(C c: A.getC()) {
    session.save(c);
}

tx.commit();


But I fail to do the (1) operation due to object references an unsaved transient instance: A.B exception. I know, that Hibernate requires all referent objects to be persistent, but anyway cannot understand why doesn't save A, get its id and use it for dependencies??? And why this piece of code works ok?:

Code:
// Cloning dependencies to another array, cleaning them in A and persisting A. Then adding copies back to A :(
List<B> copyOfB = new ArrayList<B>(A.getB());

A.getB().clear();

// The same two actions for C and D

session.save(A);

for(B b: copyOfB) {
   A.getB().add(b);

   b.setParent(A);

   session.save(b);
}
// The same for C and D...

tx.commit();


Nonsense! This is really bad code, but I have not found any other workarounds... CascadeType.SAVE_UPDATE does not suit for me, cause I want to update only CHANGED dependencies, not the whole bunch of them and produce bulk of updates after each small change.

Any opinion is welcomed :)


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Tue Jun 01, 2010 1:53 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Quote:
CascadeType.SAVE_UPDATE does not suit for me, cause I want to update only CHANGED dependencies, not the whole bunch of them and produce bulk of updates after each small change.


You should stop worry about save-update cascade. Hibernate will check what has changed and only update if there are any changes.


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Tue Jun 01, 2010 5:03 pm 
Beginner
Beginner

Joined: Wed Jul 30, 2008 8:43 am
Posts: 32
Thanks for the answer.

How is this done? Just now Hibernate updates all referencing entities, even if they were not updated. Is version field responsible for this? I currently don't use optimistic locking approach, so may be this is the reason of massive update...


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Tue Jun 01, 2010 5:15 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Quote:
How is this done?


Basically, Hibernate has a copy of the original property values and compares the old values with the current values. Collections are a bit different but here Hibernate has it's own implementations of List, Set, etc. and can monitor all changes as they are made.

Quote:
Just now Hibernate updates all referencing entities, even if they were not updated.


Can you show your code were this happens?

Quote:
Is version field responsible for this?


No.


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Tue Jun 01, 2010 5:55 pm 
Beginner
Beginner

Joined: Wed Jul 30, 2008 8:43 am
Posts: 32
Ok, some snippets. Here are my entities:
Code:
public class ContractCharges {
...
    /** */
    @OneToMany(mappedBy = "contractCharges", fetch = FetchType.LAZY)
    @Cascade( { CascadeType.DELETE_ORPHAN, CascadeType.SAVE_UPDATE })
    @Sort(type = SortType.NATURAL)
    private SortedSet<ContractChargesItem> items = new TreeSet<ContractChargesItem>();
}

public class ContractChargesItem {
...
    /** */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "contract_charges_id", nullable = false)
    private ContractCharges contractCharges;
}


Let's assume ContractCharges object is already created. We fetch it once the user came to the page:
Code:
ContractCharges entity = dao.findById(id); // Simple select fetching all children in one query:
// "select distinct cc from ContractCharges as cc "
// + "left join fetch cc.items i left join fetch cc.expenseEstimates left join fetch cc.attachedDocuments "
// + "left join fetch cc.attachedComputerDocuments where cc.id = :id"

And this object is stored in seam page context. As it lives through several http requests and session is closed - by the time of save it is dettached.
User can change contract charge or add new children:
Code:
ContractChargesItem item = new ContractChargesItem();
entity.getItems().add(Item);


When the save operation is called, I simply call saveOrUpdate operation:
Code:
....
session.saveOrUpdate(object); // This is the contractCharges object
....


And even if I changed only date for ContractCharges object - all descendants are updated as though...

Also, I checked in debugger, that the real Set for "items" fields is PersistentSortedSet, so Hibernate should support "dirtiness" of collection itself. Before the saveOrUpdate operation I watched sets and found the propety "dirty" which is "false" and "initialized=true". But anyway the update for all set items happens on commit...


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Wed Jun 02, 2010 1:52 am 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Code:
session.saveOrUpdate(object)


Here you are telling Hibernate that the object has been updated. Since it has been detached Hibernate doesn't know the original values and always assumes that it has changed. If you have not changed the object while it was detached it is better to use session.lock(object, LockMode.NONE). After this call it is safe to modify the object and Hibernate will detect changes. Another option is to use session.merge(object). Then Hibernate will first load the existing object from the database and use that to compare if it has been modified or not.


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Wed Jun 02, 2010 5:53 am 
Beginner
Beginner

Joined: Wed Jul 30, 2008 8:43 am
Posts: 32
nordborg wrote:
Code:
session.saveOrUpdate(object)


Here you are telling Hibernate that the object has been updated. Since it has been detached Hibernate doesn't know the original values and always assumes that it has changed.

Ok, I thought detached objects keep their state automatically - e.g. PersistentSet wraps core Set and marks collection changed on each set operation. But also some operations as changing of entity attribute seem to be uncatchable (e.g. setDate()) as no proxy is created for entity.

nordborg wrote:
If you have not changed the object while it was detached it is better to use session.lock(object, LockMode.NONE). After this call it is safe to modify the object and Hibernate will detect changes.


So, in this case all changes should be done inside open session? I still don't know, how Hibernate traces changes in the object if no proxy is used. Anyway this doesn't suit my case, as all changes are done to detached object and transaction is started only to persist changes..

nordborg wrote:
Another option is to use session.merge(object). Then Hibernate will first load the existing object from the database and use that to compare if it has been modified or not.


Seems this is the best way, I will try this. I always used saveOrUpdate() and thought its Hibernate analogue for persist() and merge() in JPA.

Thanks for advice!


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Wed Jun 02, 2010 7:08 am 
Beginner
Beginner

Joined: Wed Jul 30, 2008 8:43 am
Posts: 32
fitech wrote:
nordborg wrote:
Another option is to use session.merge(object). Then Hibernate will first load the existing object from the database and use that to compare if it has been modified or not.


Seems this is the best way, I will try this. I always used saveOrUpdate() and thought its Hibernate analogue for persist() and merge() in JPA.


Ok, tried this. Can try to do some conclusions:
    1. Cascade Merge works best for the relation object <- childrenCollection. In this case the only one select with left outer join is generated and compared with current detached object.
    2. Cascade Merge works worse for the relations object <- childrenCollection1, childrenCollection2, childrenCollection2. It produces 3 selects, one with left outer join between object and childrenCollection1 and others - to fetch childrenCollection2 and childrenCollection3 collections. Very pity, that they are not fetched in one select (Fetch.EAGER is not good for me, and by the way for anyone, cause will affect any other selects even if they do not need collections fetching)
    3. Cascade Merge works the worst for 3 and more levels relations. E.g. object <- childrenCollection1 <- childrenCollection2, childrenCollection3. In this case one left outer join is produced and two selects for childrenCollection2, childrenCollection3 FOR EVERY childrenCollection1 ITEM !! So, I will not use merge for 3 level relationship as this is exactly my situation and huge amount of selects is not what I need.



So, currently I'm using the approach of manual edit watch: use some kind of Map for storing user operations and during save operation merge only those objects, which were really changed:
Code:
// Saving the upper entity
super.saveOrUpdate(contract);

// Merging only those subentities, which were changed during UI interaction
for (Entry<Persistable, Utils.CRUD> charge : contract.getChangeMap().entrySet()) {
   if (charge.getKey() instanceof ContractCharges) {
      switch (charge.getValue()) {
         case CREATE:
         case UPDATE: {
                   getSession().merge(charge.getKey());
         }
      }
   }
}


Hope this helps anyone in the same situation.


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Wed Jun 02, 2010 9:25 am 
Newbie

Joined: Mon Apr 06, 2009 9:55 pm
Posts: 12
Hi fitech,

You don't have to rely on hibernate to select all these multilevel collections.
You can pre-fetch them into the persistent context in the way you prefer (using HQL or Criteria).
If an object (or graph of objects) is loaded to the persistent context, merge() only merges them in memory and then dirty objects are flushed to the database (updates). If the object (graph of objects) is not loaded in the persistent context, merge will select the object (graph of objects) from the database and will do it on cascade i.e using multiple selects (your case) and only then dirty objects are flushed to the database (updates).

To avoid the "inefficient" loading of your collections by merge(), you can use your findById() method which will use your HQL to retrieve the whole object graph (in more efficient way). The graph will be loaded to the persistent context (note, the detached graph is still outside the persistent context).
Then you can call merge() on the detached object (graph) which will merge the detached graph with the loaded graph and only changed objects will be persisted to the database.

So, overall you can control the loading the graph of objects in the most efficient way and only dirty ones will be updated to the database.

Hope it helps.
Anton


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Wed Jun 02, 2010 10:57 am 
Beginner
Beginner

Joined: Wed Jul 30, 2008 8:43 am
Posts: 32
OMG, how simple is everything :) Thanks Anton, this helped me a lot. I thought yesterday of trying to load the entity and then call merge, but forgot to try. Now I see that everything is pretty easy and beautiful. The final code has only 3 lines:

Code:
if (!contract.isNew()) {
   findById(contract.getId());
}
getSession().merge(contract);


Thank you, guys for help!


Top
 Profile  
 
 Post subject: Re: How to save object graph WITHOUT cascade.save_or_update
PostPosted: Wed Jun 02, 2010 11:30 am 
Newbie

Joined: Mon Apr 06, 2009 9:55 pm
Posts: 12
fitech wrote:
OMG, how simple is everything :)


Sure it is! Just be careful with the merge(). It returns reference to the object in the persistent context not the detached one. And the detached objects should not be used as they do not become persistent as a result of merge(). So whenever you change the detached objects(which are now obsolete) after merge() the changes will not be propagated to the database.

Code:
if (!contract.isNew()) {
   findById(contract.getId());
}

ContractCharges newContract = getSession().merge(contract);
// contract and all its children become obsolete

//newContract becomes persistent




Anton


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.