理解Hibernate的saveOrUpdate以及持久化生命周期

4
让我们看一个简单的例子,一只狗和一只猫是朋友。这并不是罕见的事情。它比我的商业案例更有趣。
我们想要一个名为"saveFriends"的函数,它需要一个狗的名字和一只猫的名字。我们将先保存狗,然后再保存猫。为了让这个例子工作,猫将会引用回狗。我知道这不是一个理想的例子,但它很可爱,适合我们的目的。
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);
}

现在,假设我想使用示例A或示例B来拯救我的朋友。哪个更好用?

此外,示例B会不会导致“非空属性引用了空值或瞬态值”错误?我只是猜测,但我认为这是因为Dog仍然在会话中。

如果这两个示例都无法使用,请解释原因。


这些实体的标识符是什么? - mdma
@mdma 我想我们可以说它们有通常的“id”字段作为标识符。 - Stephano
@Sephano - 谢谢 - 我不得不问 - 想着也许名称被用作ID - 如果saveFriends正在创建新的短暂对象并将它们持久化,或者可能更新现有的持久实体(从名称中)并不是很清楚。 - mdma
3个回答

5

现在,假设我想使用示例A或示例B来拯救我的朋友。哪个更好用?

这两个示例都可以工作(在示例B中,Hibernate将flush the session before query executions,因此它会在选择之前插入狗),但是当你已经有了狗实例时,我不明白为什么要执行额外的选择(示例B)。我会选择示例A。

此外,示例B不会导致臭名昭著的“not-null property references a null or transient value”错误吗?

不会(见上文)。在示例B中,狗不再是瞬态的。在示例A中,插入将按正确的顺序进行。

我只是猜测,但我认为这是因为Dog仍然在会话中。

第一级缓存(会话)用于按ID查找,但这里并非如此。


+1 非常好的答案。显然我需要去学习一下缓存的层级。 - Stephano
你说得对,一级缓存仅用于按ID查找,但在 felix.friend = animalDao.getDogByName(dogName) 之后,如果我没记错的话,Hibernate 保证 felix.friend == fido,对吧? - Binil Thomas
我想我明白你的意思 - 尽管在示例B之后 felix.friend == fido,但是Hibernate仍然必须执行SELECT(在刷新未提交的更改后),因为它基于非ID字段。抱歉。 :) - Binil Thomas

2
hibernate的文档中:

saveOrUpdate() 做以下事情:

如果对象在此会话中已经是持久化状态,则不执行任何操作

如果与会话相关联的另一个对象具有相同的标识符,则抛出异常

如果对象没有标识属性,则将其保存(save())

如果对象的标识符具有分配给新实例化对象的值,则将其保存(save())

如果对象由版本属性(version)或乐观锁(@Version)进行版本控制,并且版本属性值与分配给新实例化对象的值相同,则将其保存(save())

否则更新(update())该对象

就性能而言,我认为将fido指向的对象的引用赋值给它会更快,因为你不需要打开数据库连接。

关于往返旅行,+1分。我已经根据您早先的建议更改了标题。我还想知道Ex.2是否可行,或者由于该对象仍然在会话中,是否会存在某些冲突? - Stephano
我相信会执行保存操作,因为在方法调用时,既不满足“如果对象已经在此会话中持久化,则不执行任何操作”,也不满足“如果与会话关联的另一个对象具有相同的标识符,则抛出异常”。因此,该对象将被保存。当然,这可能是不正确的,但是从提供的小片段来看,我认为这是正确的。 - Woot4Moo

2
两种方法都可以正常工作,但它们的效率并不相同。在B下,因为它执行了一个查询,所以它不仅有查询本身的开销,还会强制Hibernate将更改刷新到数据库——这些更改可能一直保存在内存中,直到事务提交时才发送和其他许多更改一起发送。
与数据库的连接通常具有相当高的延迟,因此使用批处理一次发送多个更改,减少每个语句的延迟。将更改集分成许多小更改会产生相对较高的每个语句开销,而不是一组更大的更改。因此,最好在可能的情况下一起提交更改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接