使用hibernate保存带有引用依赖实体的实体

时间:2012-01-12 17:22:11

标签: java hibernate

我们使用Hibernate作为持久层,并且具有复杂的对象模型。在不暴露真实数据模型的情况下,我想使用以下简单示例解释问题。

class Person {
    private Integer id; //PK
    private String name;
    private Account account;
    // other data, setters, getters
}


class Account {
    private Integer id; //PK
    // other data, setters, getters
}

使用HBM定义DB映射如下:

 <class name="Person" table="PERSON">
    <id name="id" column="ID">
        <generator class="native"/>
    </id>
    <version name="version" type="java.lang.Long"/>
    <property name="name" type="java.lang.String" length="50" column="NAME"/>
    <many-to-one name="account" column="ACCOUNT_ID"
                class="com.mycompany.model.Account"/>

</class>

我必须保存链接到现有Person的新填充Account实例。该调用由Web客户端发起,因此在我的层中,我获得了引用Account实例的Person实例,该实例仅包含其ID。

如果我尝试调用saveOrUpdate(person),则抛出以下异常:

org.hibernate.TransientObjectException: 
object references an unsaved transient instance - save the transient instance before flushing: 
com.mycompany.model.Account

为了避免这种情况,我必须按ID找到Account的持久对象,然后调用person.setAccount(persistedAccount)。在这种情况下一切正常。

但在现实生活中,我处理了几十个相互引用的实体。我不想为每个引用编写特殊代码。

我想知道这个问题是否存在某种通用解决方案。

6 个答案:

答案 0 :(得分:7)

要保留一个实体,您只需要对其直接依赖项进行引用。这些其他实体引用其他实体的事实并不重要。

最好的方法是使用session.load(Account.class, accountId)获取引用实体的代理,甚至无需访问数据库。

您正在做的事情是正确的:获取对持久帐户的引用,并将此引用设置为新创建的帐户。

答案 1 :(得分:2)

Hibernate允许你只使用带ID的引用类,你不需要做session.load()。

唯一重要的是,如果您的引用对象具有VERSION,则必须设置版本。 在您的情况下,您必须指定帐户对象的版本。

答案 2 :(得分:1)

在* -to- * mapping

上使用cascade="all"

答案 3 :(得分:0)

您是否在cascade="save-update"元素中尝试了many-to-one? Hibernate默认为cascade="none" ...

答案 4 :(得分:0)

谢谢你的帮助。我已经实现了自己的通用解决方案,但想知道是否存在其他解决方案。

我想与您分享这个想法。我将引用的实体称为除ID以外的任何内容(或其他可用于唯一标识实体的字段)placeholder

因此,我创建了注释@Placeholder并将其放在所有引用的字段上。在我们的示例中,它位于Person类的帐户字段中。 我们已经有了一个名为GenericDao的类,它包含了Hibernate API并且有方法save()。我添加了另一种方法saveWithPlacehodlers(),它执行以下操作。它通过反射发现给定对象的类,查找标有注释@Placeholder的所有字段,在DB中查找对象并在主实体中调用适当的setter以用持久实体替换引用的占位符。

注释@Placeholder允许定义将用于标识实体的字段。默认值为id

你怎么看待这个解决方案的人?

答案 5 :(得分:0)

还有一个问题的解决方案 - 使用您想要引用的实体的默认构造函数,并设置id和version(如果它是版本化的)。我们有以下dao方法:

public <S extends T> S materialize(EId<S> entityId, Class<S> entityClass) {
        Constructor<S> c = entityClass.getDeclaredConstructor();
        c.setAccessible(true);

        S instance = c.newInstance();
        Fields.set(instance, "id", entityId.getId());
        Fields.set(instance, "version", entityId.getVersion());
        return instance; // Try catch omitted for brevity.
}

我们可以使用这种方法,因为我们不使用延迟加载,而是使用GUI上使用的实体的“视图”。这使我们能够摆脱Hibernate用来填充所有渴望关系的所有联接。视图始终具有实体的ID和版本。因此,我们可以通过创建一个Hibernate看起来不是瞬态的对象来填充引用。

我尝试了这种方法和session.load()的方法。他们都工作得很好。我看到我的方法有一些优点,因为Hibernate不会泄漏代码中其他地方的代理。如果没有正确使用,我只会得到NPE而不是'no session bound to thread'异常。