-->
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: Hibernate Common Idiom for Updating Complex Model Easily?
PostPosted: Thu Jul 06, 2006 9:17 am 
Newbie

Joined: Thu Jul 06, 2006 8:38 am
Posts: 14
Hello all,

Hibernate version: 3.1.3

I have a Parent class that has one-to-many relationship with a Child. I've read the Hibernate document about setting up Parent / Child relationship and code them accordingly, i.e.:

1. In Child.hbm.xml, I put this:

Code:
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true" />


Respectively, in Child.java, I put:
Code:
private Parent parent;
// getter and setter here


2. In Parent.hbm.xml, I put this:

Code:
<set name="children" lazy="false" inverse="true" cascade="all-delete-orphan">
    <key column="parent_id" not-null="true"/>
    <one-to-many class="Parent"/>
</set>      


In Parent.java I have:

Code:
private Set<Child> children;


and:

Code:
    public Set<Child> getChildren()
    {
        return this.children;
    }

   
    public void setChildren( Set<Child> children)
    {
        this.children = children;
    }



Now, my question is this:

I get Parent from DB, and then I populate an ActionForm with the Parent. I send ActionForm to JSP and let the user modify it as necessary, adding/updating/removing children as needed.

When the user submits, I use the ActionForm to populate back a new empty Parent (the ids are kept throughout even in JSPs as hidden inputs). Here's where the problem starts. I can modify a Child, no problem. But I can't delete a Child from Parent's children Set and having it persisted to the database.

Now, before you ask me to RTFM ;-), I am also aware of this:

Code:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();


However, we've put an abstraction layer between domain models and the data access mechanism, so we don't usually get the Hibernate Session instance directly. The flow for everything else works nicely, that is:

1. Get a model instance from DB
2. Update in UI
3. Go back to biz layer
4. save back the modified model instance to DB (eventually gets to Hibernate's saveOrUpdate());

Is there a common idiom or pattern to handle this? Is there any way of me telling Hibernate that, yes, child A has just been removed from UI, please remove it from DB as well, without breaking the established flow? As it is now I can only persist updates to children, but not children removal (Hibernate only generates UPDATE statement for any children that are left, it still doesn't touch the ones that are supposed to be removed from the DB). That is, if Parent has child A, B, C, D, then D is removed in the UI layer, instead of generating:

DELETE from CHILD where id = <D's id>

and UPDATE A, B, C

It merely generates UPDATE A, B, C, and leaves D untouched.

How do you guys usually do it? I'm sure there's a common idiom for this? I'm using Spring if that will help...

Thank you very much,
Ray


Top
 Profile  
 
 Post subject: By the way, adding a Child to the set works because...
PostPosted: Thu Jul 06, 2006 9:36 am 
Newbie

Joined: Thu Jul 06, 2006 8:38 am
Posts: 14
in the setChildren() method I can do this:

Code:
public void setChildren(Set<Child> children) {
    for(Child child : children) {
        child.setParent(this);
    }
    this.children = children;
}


So no NOT-NULL constraint violation there.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 8:18 pm 
Newbie

Joined: Thu Jul 06, 2006 8:38 am
Posts: 14
Can somebody help, please? I have RTFM, so please bear with the beginning of the post which looks like a typical parent-child question that has been asked so many times before.

I'm asking for the typical idiom, is it possible that you do that from the Parent only, e.g.: saveOrUpdate(Parent).

Thanks,
Ray


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 10:46 pm 
Newbie

Joined: Thu Jul 06, 2006 8:38 am
Posts: 14
Why is all-delete-orphan not working for my case? As I understand it from the doc

http://www.hibernate.org/hib_docs/v3/re ... transitive

Quote:
A special cascade style, delete-orphan, applies only to one-to-many associations, and indicates that the delete() operation should be applied to any child object that is removed from the association.



Then why isn't it working for my case? Is it a bug? If I miss something from TFM please point me. I've done a search and reread Hibernate docs many times.

Thanks
Ray


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 11:04 pm 
Regular
Regular

Joined: Tue May 16, 2006 3:32 am
Posts: 117
Are you changing the instance of 'children'? Is the original collection returned by Hibernate replaced with an instance of a new collection with the latest children to be updated?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 11:07 pm 
Newbie

Joined: Thu Jul 06, 2006 8:38 am
Posts: 14
JayeshJ wrote:
Are you changing the instance of 'children'? Is the original collection returned by Hibernate replaced with an instance of a new collection with the latest children to be updated?


Yeah, it is. I read the document where you're supposed to keep the get and set for the collection simple, like this:

public Set<Child> getChildren()
{
return this.children;
}


public void setChildren( Set<Child> children)
{
this.children = children;
}

Also, I set all-delete-orphan for cascading. So what did I do wrong, any ideas?

By the way the instance is detached though. So I send it to the UI for updating. Then when the user saves, I create an empty Parent instance, populates the values from the form, and call saveOrUpdate() on the Parent.

I tried calling merge() but I got this (in this real example, Vendor is Parent, and contacts are the Children collection):

2006-07-07 10:54:50,580 INFO [org.hibernate.event.def.DefaultLoadEventListener] - Error performing load command
org.hibernate.PropertyAccessException: exception setting property value with CGLIB (set hibernate.cglib.use_reflection_o
ptimizer=false for more info) setter of com.barcap.gcs.gmevents.model.Vendor.setContacts
at org.hibernate.tuple.PojoEntityTuplizer.setPropertyValuesWithOptimizer(PojoEntityTuplizer.java:215)
at org.hibernate.tuple.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:185)
at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:3232)
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:126)
at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:842)
at org.hibernate.loader.Loader.doQuery(Loader.java:717)


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 11:15 pm 
Regular
Regular

Joined: Tue May 16, 2006 3:32 am
Posts: 117
Hibernate replaces the Set with its own implentation of a Set when an object is persisted or fetched from the database. Within this implementation it keeps track of what was there initially. For orphan delete to work this information is necessary. So if you use a new collection Hibernate would just update, but there would be no deletion. Use the same instance of the collection. Clear the collection and then add the new contents. That should work.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 11:23 pm 
Newbie

Joined: Thu Jul 06, 2006 8:38 am
Posts: 14
JayeshJ wrote:
Hibernate replaces the Set with its own implentation of a Set when an object is persisted or fetched from the database. Within this implementation it keeps track of what was there initially. For orphan delete to work this information is necessary. So if you use a new collection Hibernate would just update, but there would be no deletion. Use the same instance of the collection. Clear the collection and then add the new contents. That should work.


Thanks Jayesh, but in the first place what I get from the UI layer is a detached instance (with "vanilla" Set instead of PersistentSet).

So to do what you said I need to load the original instance first from the DB don't I? E.g.:

Parent persistentParent = Session.load(id);

// oops this will change teh PersistentSet to Set again
BeanUtils.copyProperties(persistentParent, parent);

So I wonder, what is the common idiom for this? Surely there have been a lot of people doing this--is there a common idiom for doing this?

Thanks,
Ray


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 11:26 pm 
Regular
Regular

Joined: Tue May 16, 2006 3:32 am
Posts: 117
E.g.

Set children; // Hibernate's


Set childrenUI; // collection with latest updates..


children.clear();
children.addAll(childrenUI);


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 06, 2006 11:34 pm 
Newbie

Joined: Thu Jul 06, 2006 8:38 am
Posts: 14
JayeshJ wrote:
E.g.

Set children; // Hibernate's


Set childrenUI; // collection with latest updates..


children.clear();
children.addAll(childrenUI);


Hi Jayesh,

Yes, I know that. but my question is, your bean will typically be quite huge, and it contains a mix of collections and simple types. E.g.: A company will have:

public class Company {
private String name;
private Set<Contacts> contacts;
}

If we do the "clear() and then addAll()" that means we cannot for instance use apache's BeanUtil to copy the properties, rather we need to set manually one by one.

Cheers
Ray


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 07, 2006 1:36 am 
Regular
Regular

Joined: Tue May 16, 2006 3:32 am
Posts: 117
The normal way to do this would be to directly use the detached instance instead of creating a new object, populating this and then copying the values again to another detached instance, especially if you want orphan delete to work. Do you really need to create a new object?

Still, if you want it this way...

I have never used BeanUtils. Assuming it requires get/set methods, you could do something like this (I have not tried this out. You may have to redo a bit, but I feel that this should work) :-

btw writing code in get/set is not really a problem so long as you know what is happening.


Code:

public class Company {
private String name;
private Set<Contacts> contacts;

private Set<Contacts> contactsReference; // internal use
// this does not have any get/set.


public void setContacts(Set contacts) {
  this.contacts = contacts;

  if (contacts is an instance of HashSet) {
//    (assuming that you would always be creating instances of HashSet for new objects)
        if (contactsReference != null)  {
           contactsRerence.clear();
           contactsRerence.addAll(contacts);
           this.contacts = contactsReference;
        }

  } else { 
      contactReference = contacts;
  }

  public Set getContacts() {
     return this.contacts;
  }

}



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.