使用休眠保存实体与引用的依赖实体

问题描述:

我们使用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如下定义:

<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> 

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

如果我尝试调用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)。在这种情况下,一切正常。

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

我想知道是否有这种问题的某种通用解决方案。

使用cascade="all"要坚持一个实体,你只需要有它的直接依赖的引用。这些其他实体引用其他实体并不重要。

做到这一点,最好的办法是让一个代理引用的实体,甚至没有进入数据库,使用session.load(Account.class, accountId)

你在做什么是应该做的正确的事情:得到永久账号的参考,并将此引用到新创建的帐户。

+0

谢谢你,@JB Nizet。这实际上是我的预期......我会在这里发布我的通用解决方案的描述,并很乐意知道您的意见。 – AlexR 2012-01-12 17:50:23

上的* *至 - 映射

+0

在问题中没有*对多映射... – 2012-01-12 17:31:35

+0

@Bozho,我加了'cascade'。其实我只是忘了提及我已经玩过了。现在,当我尝试保存具有合法ID的临时帐户的人时,我得到如下异常:org.hibernate.AssertionFailure:com.mycompany.model.Person条目中的空id(在发生异常后不刷新会话)' – AlexR 2012-01-12 17:45:08

+0

@JB Nizet,你写。它是'多对一' – AlexR 2012-01-12 17:46:02

有你在many-to-one元素试过cascade="save-update"?休眠默认为cascade="none" ...

+0

我试过了@Nacho。它无法正常工作。它会抛出其他异常(请参阅我对Bozho的回答的评论) – AlexR 2012-01-12 17:48:33

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

我想与大家分享这个想法。我呼叫除ID之外不包含任何内容的参考实体(或可用于唯一标识实体的其他字段)placeholder

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

注释@Placeholder允许定义字段将被用于识别实体。默认值是id

您认为什么人,关于这个解决方案的人?

+0

这些“自动”解决方案必须小心处理,如果我理解您的描述正确,您将解决一次处理一个对象的Save方法中的依赖关系。你在一个工作单元中保存了很多对象,你正在逐个加载所有的依赖对象,这是非常低效的。通常,数据库操作是由一个存储库或一个服务类来完成的,两者都应该有一个好的他们可以一次加载大量实体​​(例如使用in子句),也可以验证是否已经存在依赖实体。 – oddparity 2013-08-24 18:58:25

Hibernate允许您仅使用带ID的引用类,您不需要执行session.load()。

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

+0

这是在Hibernate文档还是其他可用文档中描述的? – sbrattla 2016-06-16 20:07:28

+0

爱你man!我把这个版本添加到我的课程中,我不能通过设置ID来保存它,浪费了很多时间 – 2016-12-18 13:07:52

还有一个问题的解决方案 - 使用您想要引用的实体的默认构造函数,并设置id和版本(如果它是版本化的)。我们有以下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而不是'没有会话绑定到线程'异常。