tags:

views:

145

answers:

3

Let's look at a simple example of a dog and a cat that are friends. This isn't a rare occurrence. It also has the benefit of being much more interesting than my business case.

We want a function called "saveFriends" that takes a dog name and a cat name. We'll save the Dog and then the Cat. For this example to work, the cat is going to have a reference back to the dog. I understand this isn't an ideal example, but it's cute and works for our purposes.

FriendService.java

public int saveFriends(String dogName, String catName) {
    Dog fido = new Dog();
    Cat felix = new Cat();

    fido.name = dogName;
    fido = animalDao.saveDog(fido);

    felix.name = catName;
    [ex.A]felix.friend = fido;
    [ex.B]felix.friend = animalDao.getDogByName(dogName);
    animalDao.saveCat(felix);
}

AnimalDao.java (extends HibernateDaoSupport)

public Dog saveDog(Dog dog) {
    getHibernateTemplate().saveOrUpdate(dog);
    return dog
}

public Cat saveCat(Cat cat) {
    getHibernateTemplate().saveOrUpdate(cat);
    return cat;
}

public Dog getDogByName(String name) {
    return (Dog) getHibernateTemplate().find("from Dog where name=?", name).get(0);
}

Now, assume for a minute that I would like to use either example A or example B to save my friend. Is one better than the other to use?

Furthermore, won't example B get you the infamous "not-null property references a null or transient value" error? I'm only guessing here but I would think this is because the Dog is still in the session.

I'll understand if neither of those examples work, but please explain why.

+1  A: 

From the docs at hibernate

saveOrUpdate() does the following:

if the object is already persistent in this session, do nothing

if another object associated with the session has the same identifier, throw an exception

if the object has no identifier property, save() it

if the object's identifier has the value assigned to a newly instantiated object, save() it

if the object is versioned by a or , and the version property value is the same value assigned to a newly instantiated object, save() it

otherwise update() the object

Performance wise I would say assigning the reference to the object that fido points to would be faster since you are not opening a connection to the database.

Woot4Moo
+1 good point about the round trip. I've changed the title per your earlier suggestion. I guess I'm also curious if Ex.2 would work, or if there would be some conflict since that object is still in the session?
Stephano
I believe a save would be executed, because neither "if the object is already persistent in this session, do nothing " nor "if another object associated with the session has the same identifier, throw an exception " is true at the time of method invocation. So therefore the object would be saved. That of course may be incorrect but looking at the small snippet provided I believe that is proper.
Woot4Moo
+4  A: 

Now, assume for a minute that I would like to use either example A or example B to save my friend. Is one better than the other to use?

Both examples will work (in example B, Hibernate will flush the session before query executions so it will insert the dog before the select) but I don't see the point of doing an extra select (example B) when you already have the dog instance. I would just go for example A.

Furthermore, won't example B get you the infamous "not-null property references a null or transient value" error?

No (see above). In example B, the dog is not transient anymore. In example A, inserts will be done in the right order.

I'm only guessing here but I would think this is because the Dog is still in the session.

The first level cache (the session) is used for find by id, which is not the case here.

Pascal Thivent
+1 Very good answers. Clearly I need to go brush up on the levels of caching.
Stephano
@Pascal you are right that the first-level cache is used only for by-id lookups, but after `felix.friend = animalDao.getDogByName(dogName)`, if I remember correctly, Hibernate guarantees `felix.friend == fido`, rt?
binil
(assuming `name` is a unique property of `Dog`)
binil
@Pascal, I think I understand what you were saying - although after example B `felix.friend == fido`, Hibernate still has to do the `SELECT` (after flushing uncommitted changes) because it is based on a non-id field. My apologies. :)
binil
+2  A: 

Both will work correctly but they are not as equally efficient. Under B, because it executes a query, not only does it have the overhead of the query itself, but it forces hibernate to flush changes to the database - changes that might have been kept in memory until the transaction was committed, and sent with many other changes.

Connections to the db typically suffer quite high latency, so batching is used to send many changes at once, reducing the latency per statement. Breaking up the change set into lots of small changes incurrs a relatively higher overhead per statement than a larger group of changes. So, it's best to submit changes all together, when possible.

mdma
Good point about the extra cost of the "too early flush". +1
Pascal Thivent