我们使用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)
。在这种情况下一切正常。
但在现实生活中,我处理了几十个相互引用的实体。我不想为每个引用编写特殊代码。
我想知道这个问题是否存在某种通用解决方案。
答案 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'异常。