使用业务密钥相等进行级联更新:Hibernate最佳实践?
我对Hibernate还不熟悉,虽然有大量的例子要看,但这里似乎有太多的灵活性,以至于有时很难将所有选项缩小到最佳方式。我已经在一个项目上工作了一段时间,尽管读了很多书、文章和论坛,我还是有点挠头。任何经验丰富的建议都将不胜感激 所以,我有一个模型,涉及两个类,从父到子,有一对多的关系。每个类都有一个代理主键和一个唯一受约束的复合业务键使用业务密钥相等进行级联更新:Hibernate最佳实践?,hibernate,Hibernate,我对Hibernate还不熟悉,虽然有大量的例子要看,但这里似乎有太多的灵活性,以至于有时很难将所有选项缩小到最佳方式。我已经在一个项目上工作了一段时间,尽管读了很多书、文章和论坛,我还是有点挠头。任何经验丰富的建议都将不胜感激 所以,我有一个模型,涉及两个类,从父到子,有一对多的关系。每个类都有一个代理主键和一个唯一受约束的复合业务键 <class name="Container"> <id name="id" type="java.lang.Long">
<class name="Container">
<id name="id" type="java.lang.Long">
<generator class="identity"/>
</id>
<properties name="containerBusinessKey" unique="true" update="false">
<property name="name" not-null="true"/>
<property name="owner" not-null="true"/>
</properties>
<set name="items" inverse="true" cascade="all-delete-orphan">
<key column="container" not-null="true"/>
<one-to-many class="Item"/>
</set>
</class>
<class name="Item">
<id name="id" type="java.lang.Long">
<generator class="identity"/>
</id>
<properties name="itemBusinessKey" unique="true" update="false">
<property name="type" not-null="true"/>
<property name="color" not-null="true"/>
</properties>
<many-to-one name="container" not-null="true" update="false"
class="Container"/>
</class>
第一次一切正常,并且容器
及其项
都被持久化。但是,如果再次执行上述代码块,Hibernate将抛出一个ConstraintViolationException
——重复“name”和“owner”列的值。因为新的容器
实例的标识符为空,所以Hibernate假定它是一个未保存的临时实例。这是预期的,但不是期望的。由于持久和暂时的容器
对象具有相同的业务键值,因此我们真正想要的是发布更新
很容易让Hibernate相信新的容器
实例与旧实例相同。通过快速查询,我们可以获得要更新的容器的标识符,并将瞬态对象的标识符设置为匹配
Container c = new Container("Things", "Me");
c.addItem(new Item("String", "Blue"));
c.addItem(new Item("Wax", "Red"));
Query query = session.createSQLQuery("SELECT id FROM Container" +
"WHERE name = ? AND owner = ?");
query.setString(0, c.getName());
query.setString(1, c.getOwner());
BigInteger id = (BigInteger)query.uniqueResult();
if (id != null) {
c.setId(id.longValue());
}
Transaction t = session.beginTransaction();
session.saveOrUpdate(c);
t.commit();
这几乎满足了Hibernate的要求,但由于从容器到项的一对多关系级联,因此也会为子项
对象抛出相同的ConstraintViolationException
我的问题是:在这种情况下,什么是最佳做法?强烈建议使用代理主键,也建议使用业务键相等。然而,当您将这两个建议放在一起实践时,Hibernate的两个最大的便利——saveOrUpdate和级联操作——似乎变得几乎完全无用。在我看来,我只有两个选择:
- 手动获取并设置映射中每个对象的标识符。这显然是可行的,但对于一个中等规模的模式来说,这是一个很大的额外工作,Hibernate似乎很容易做到
- 编写一个自定义拦截器来获取和设置每个操作的对象标识符。这看起来比第一个选项更简洁,但相当繁重,而且我认为应该编写一个插件来覆盖Hibernate的默认行为,以实现遵循推荐设计的映射,这似乎是错误的
有更好的办法吗?我的假设完全错了吗?我希望我只是错过了一些东西
谢谢。在添加孩子之前先装入容器(无论如何,这是个好主意),这样您就可以利用“设置”行为。将项目添加到容器时,将不会添加该项目
哦,您还需要确保覆盖项中的equals和hashcode,以确保事情按您期望的方式运行
所以
这两个建议(代理主键、业务主键)冲突,因为它们是针对不同的上下文推荐的。使用代理主键,面向对象编程似乎做得更好。Hibernate希望您进行面向对象编程,因为Hibernate的目的是允许您在透明地将对象持久化到数据库的同时进行面向对象编程。这似乎并没有回答这个问题。你业余时间是政治家吗?;-)你能为这个问题提供更深层次的见解或解决方案吗?
Container c = new Container("Things", "Me");
c.addItem(new Item("String", "Blue"));
c.addItem(new Item("Wax", "Red"));
Query query = session.createSQLQuery("SELECT id FROM Container" +
"WHERE name = ? AND owner = ?");
query.setString(0, c.getName());
query.setString(1, c.getOwner());
BigInteger id = (BigInteger)query.uniqueResult();
if (id != null) {
c.setId(id.longValue());
}
Transaction t = session.beginTransaction();
session.saveOrUpdate(c);
t.commit();
Query query = session.createSQLQuery("FROM Container " +
"WHERE name = ? AND owner = ?");
query.setString(0, "Things");
query.setString(1, "Me");
Container c = (BigInteger)query.uniqueResult();
c.addItem(new Item("String", "Blue"));
c.addItem(new Item("Wax", "Red"));
Transaction t = session.beginTransaction();
session.saveOrUpdate(c);
t.commit();