我们使用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客户端发起,因此在我的层中,我获得了实例的Person实例 Account
仅保留其ID。
如果我试着打电话 saveOrUpdate(person)
抛出以下异常:
org.hibernate.TransientObjectException:
object references an unsaved transient instance - save the transient instance before flushing:
com.mycompany.model.Account
为了避免这种情况,我必须找到持久化的对象 Account
按ID然后调用 person.setAccount(persistedAccount)
。在这种情况下一切正常。
但在现实生活中,我处理了几十个相互引用的实体。我不想为每个引用编写特殊代码。
我想知道这个问题是否存在某种通用解决方案。
要保留一个实体,您只需要对其直接依赖项进行引用。这些其他实体引用其他实体的事实并不重要。
最好的方法是使用代理来获取引用实体的代理,甚至不需要访问数据库 session.load(Account.class, accountId)
。
您正在做的是正确的事情:获取对持久帐户的引用,并将此引用设置为新创建的帐户。
要保留一个实体,您只需要对其直接依赖项进行引用。这些其他实体引用其他实体的事实并不重要。
最好的方法是使用代理来获取引用实体的代理,甚至不需要访问数据库 session.load(Account.class, accountId)
。
您正在做的是正确的事情:获取对持久帐户的引用,并将此引用设置为新创建的帐户。
Hibernate允许你只使用带ID的引用类,你不需要做session.load()。
唯一重要的是,如果您的引用对象具有VERSION,则必须设置版本。
在您的情况下,您必须指定帐户对象的版本。
使用 cascade="all"
在* -to- *映射上
你有没有尝试过 cascade="save-update"
在 many-to-one
元件? Hibernate默认为 cascade="none"
...
感谢您的帮助。我已经实现了自己的通用解决方案,但想知道是否存在其他解决方案。
我想和你分享这个想法。我调用的引用实体除了ID(或其他可用于唯一标识实体的字段)之外不包含任何内容 placeholder
。
因此,我创建了注释@Placeholder并将其放在所有引用的字段上。在我们的示例中,它位于Person类的帐户字段中。
我们已经有了课程名称 GenericDao
包装Hibernate API并拥有方法 save()
。我添加了另一种方法 saveWithPlacehodlers()
这样做会有以下情况。它通过反射发现给定对象的类,查找标有注释的所有字段 @Placeholder
,在DB中查找对象并在主实体中调用适当的setter以用持久实体替换引用的占位符。
注解 @Placeholder
允许定义将用于标识实体的字段。默认是 id
。
关于这个解决方案,你觉得怎么样?
还有一个问题的解决方案 - 使用您想要引用的实体的默认构造函数,并设置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'异常。