views:

938

answers:

3

Hello - I ahve asked this on the Hibernate forums but no one seems to have been able to provide an answer so I am trying here! Thanks chaps!

I have a collection in my model that contains a set of 'previous versions' of my root domain object. The previous versions are therefore 'immutable' and we will never want to update them, and only want to add past versions as they arise. Also the 'versioned' domain object is fairly complex and causes heavy database access to retrieve.

When I have a new version of one of these objects I want to save it with the others without loading the entire set. The Advanced FAQ has some advice on this:

Why does Hibernate always initialize a collection when I only want to add or remove an element?

Unfortunately the collections API defines method return values that may only be computed by hitting the database. There are three exceptions to this: Hibernate can add to a <bag>, <idbag> or <list> declared with inverse="true" without initializing the collection; the return value must always be true.

If you want to avoid extra database traffic (ie. in performance critical code), refactor your model to use only many-to-one associations. This is almost always possible. Then use queries in place of collection access.

I am new to all of this and am not 100% sure on how to refactor your model to use only many-to-one associations. Can anyone please give me an example of point me to a tutorial so that I can learn how this will resolves my issue?

Thanks

Ori

+1  A: 

the way this is typically done by me, is to define the collection as "inverse".

that roughly means: the primary definition of the 1-N association is done at the "N" end. if you want to add something to the collection you alter the associated object of the detail data.

a small xml example:

<class name="common.hibernate.Person" table="person">
 <id name="id" type="long" column="PERSON_ID">
  <generator class="assigned"/>
 </id>
 <property name="name"/>
    <bag name="adressen" inverse="true">
  <key column="PERSON_ID"/>
  <one-to-many class="common.hibernate.Adresse"/>
 </bag>
 </class>

 <class name="common.hibernate.Adresse" table="ADRESSE">
 <id name="id" column="ADRESSE_ID"/>
 <property name="street"/>
 <many-to-one name="person" column="PERSON_ID"/>
   </class>

then the update is done exclusively in Adresse:

Adresse a = ...;
a.setPerson(me);
a.setStreet("abc");
Session s = ...;
s.save(a);

done. dou did not even touch the collection. consider it read-only, which may be very practical for querying with hql, and iterating and displaying it.

Andreas Petersson
Many thanks for your time and advice.This is an excellent suggestion. I looked into this, but, unfortunately I had two conflicting issues: 1) The 'managed-sub-components' are from an external system so I cannot add the necessary inverse association to them; 2) furthermore the owning entity already declares the 'type' of the 'managed-sub-component' in a superclass making it difficult to create and intermediate wrapper.However, this advice would be suitable for others in a similar situation I feel.Thanks!
Ori
a.setPerson(me) => now I understand! Thanks!
Karussell
A: 

If I understand well your need:

  • you have an in-memory add-only collection of immutable objects
  • how to add an item to that collection without initializing the entire collection (in the current Session)?

Did you consider maintaining your collection disconnected?

  • Load it once when the application starts
  • Adding an element would mean two operations, done by a single service in only one place:
    • save the element (fast database operation, no cascading is done to the parent)
    • add the element to the existing disconnected collection (no database operation)
KLE
Many thanks for your time and advice. There is a top-level entity that maintains a single collection (a Map) of the previous and the current versions of the managed sub-component.The user can specify arbitrary operations on the current managed sub-component. If any of these operations result in a modification to the sub-component then the current version should become the previous version and the new altered version the 'head'.I will look into the removal of the cascading operation and handling it manually then. Thanks!
Ori
OK, glad I could help you.
KLE
+5  A: 

Hi,

As said

Why does Hibernate always initialize a collection when i only want to add or remove an element ?

I suppose the following according to your question:

@Entity
public class Root {

    private Set<PreviousVersion> pvSet;

    @OneToMany
    @Cascade(cascadeType=SAVE_UPDATE)
    @JoinColumn("ROOT_ID")
    public Set<PreviousVersion> getPvSet() {
        return pvSet;
    }

}

If you do something like

// Notice load method instead of get

Roor root = session.load(Root.class, rootId)

When you call load method, Hibernate loads a proxy object. It does not hit the database

So when you call

root.getPvSet().add(new PreviousVersion()); again it does not hit the database. It only records new PreviousVersion() in database whithout load any PreviousVersion object instead of. But if you call something like

root.getGetPvSet().size()

So Hibernate needs to hit the database because size() method.

Take care of the following

  1. If your collection is a Set of composite objects (@CollectionOfElements) Hibernate ALWAYS will hit the database because it compare one by one PreviousVersion object by using equals implementation before saving or updating.
  2. Does your Set collection with fetch EAGER ? If so then unless you use a Hibernate Query language your collection will always be fetched. Use fetch LAZY instead

regards,

Arthur Ronald F D Garcia
I think it does read the current collection in order to do the add. This is because it needs to check the `Set` constraints. i.e. it needs to see if the entity that you're adding is already in the collection, as it has to return false if it is already there.
davidsheldon