首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >与业务密钥相等的级联更新: Hibernate最佳实践?

与业务密钥相等的级联更新: Hibernate最佳实践?
EN

Stack Overflow用户
提问于 2009-06-17 17:37:26
回答 2查看 1.2K关注 0票数 1

我刚开始使用Hibernate,虽然有大量的例子要看,但这里似乎有太多的灵活性,有时很难缩小所有的选项--最好的方法。我做一个项目已经有一段时间了,尽管我读了很多书、文章和论坛,但我还是有点头脑发热。任何经验丰富的建议都将不胜感激。

因此,我有一个模型,涉及两个类,一对多的关系从父母到孩子。每个类都有一个代理主键和一个受唯一约束的复合业务密钥。

代码语言:javascript
复制
<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>

这些映射背后的bean是你所能想象到的最无聊的东西--没有什么好想象的。考虑到这一点,请考虑以下代码:

代码语言:javascript
复制
Container c = new Container("Things", "Me");
c.addItem(new Item("String", "Blue"));
c.addItem(new Item("Wax", "Red"));

Transaction t = session.beginTransaction();
session.saveOrUpdate(c);
t.commit();

第一次一切都很好,ContainerItem都是持久化的。但是,如果再次执行上述代码块,Hibernate将抛出"name“和"owner”列的重复值ConstraintViolationException。因为新的Container实例有一个空标识符,Hibernate假定它是一个未保存的瞬态实例。这是预期的,但不是期望的。由于持久和瞬态Container对象具有相同的业务键值,所以我们真正想要的是发出更新。

让Hibernate相信我们的新Container实例和旧的实例是一样的,这是非常容易的。通过快速查询,我们可以获得要更新的Container的标识符,并将瞬态对象的标识符设置为匹配。

代码语言:javascript
复制
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的要求,但是由于从Container到项目的一对多关系级联,所以对于子Item对象也会抛出相同的ConstraintViolationException

我的问题是:在这种情况下,最佳做法是什么?强烈建议使用代理项主键,还建议使用业务密钥相等。但是,当您将这两项建议结合在一起时,Hibernate的两个最大便利--saveOrUpdate和级联操作--似乎几乎完全没有用处。在我看来,我只有两种选择:

  • 手动获取并设置映射中每个对象的标识符。这显然是可行的,但对于中等大小的模式来说,这似乎是Hibernate可以轻松完成的大量额外工作。
  • 编写了一个自定义拦截器来获取和设置每个操作的对象标识符。这看起来比第一个选项更干净,但却相当沉重,在我看来,应该要求您编写一个插件来覆盖Hibernate的默认行为,以便按照推荐的设计进行映射。

有更好的办法吗?我是不是做错了假设?我希望我只是错过了什么。

谢谢。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2009-06-17 17:47:40

在添加子容器之前加载容器(不管怎样,这是一个好主意),这样您就可以利用“set”行为。当您将项目添加到容器中时,它将不会被添加。

哦,您还需要确保在Item中覆盖等于和hashcode,以确保事情按照预期的方式工作。

所以..。

代码语言:javascript
复制
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();
票数 1
EN

Stack Overflow用户

发布于 2009-06-17 17:59:18

这两项建议(代理主键、业务主键)相互冲突,因为它们被推荐用于不同的上下文。面向对象的编程似乎在代理主键方面做得更好。Hibernate期望您进行面向对象的编程,因为Hibernate的目的是允许您在透明地将对象持久化到数据库的同时进行面向对象的编程。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1008519

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档