【发布时间】:2010-10-19 04:11:00
【问题描述】:
在我的具体情况下,我正在使用鉴别器列策略。这意味着我的 JPA 实现(Hibernate)创建了一个具有特殊 DTYPE 列的 users 表。此列包含实体的类名。例如,我的 users 表可以有 TrialUser 和 PayingUser 的子类。这些类名将位于 DTYPE 列中,以便 EntityManager 从数据库加载实体时,它知道要实例化哪种类型的类。
我尝试了两种转换 Entity 类型的方法,都感觉像肮脏的 hack:
- 使用本机查询手动对列执行更新,更改其值。这适用于属性约束相似的实体。
- 创建目标类型的新实体,执行 BeanUtils.copyProperties() 调用以移动属性,保存新实体,然后调用命名查询,手动将新 ID 替换为旧 ID,以便维护所有外键约束。
#1 的问题在于,当您手动更改此列时,JPA 不知道如何将此实体刷新/重新附加到持久性上下文。它需要一个 ID 为 1234 的 TrialUser,而不是一个 ID 为 1234 的 PayingUser。它失败了。在这里,我可能会做一个 EntityManager.clear() 并分离所有实体/清除 Per。上下文,但由于这是一个服务 bean,它会擦除系统所有用户的未决更改。
#2 的问题是,当您删除 TrialUser 时,您设置为 Cascade=ALL 的所有属性也将被删除。这很糟糕,因为您只是尝试换入不同的用户,而不是删除所有扩展对象图。
更新 1:#2 的问题让我几乎无法使用它,所以我放弃了尝试让它工作。更优雅的 hack 绝对是 #1,我在这方面取得了一些进展。关键是首先获取对底层 Hibernate Session 的引用(如果您使用 Hibernate 作为 JPA 实现)并调用 Session.evict(user) 方法从持久性上下文中仅删除该单个对象。不幸的是,没有纯粹的 JPA 支持。下面是一些示例代码:
// Make sure we save any pending changes
user = saveUser(user);
// Remove the User instance from the persistence context
final Session session = (Session) entityManager.getDelegate();
session.evict(user);
// Update the DTYPE
final String sqlString = "update user set user.DTYPE = '" + targetClass.getSimpleName() + "' where user.id = :id";
final Query query = entityManager.createNativeQuery(sqlString);
query.setParameter("id", user.getId());
query.executeUpdate();
entityManager.flush(); // *** PROBLEM HERE ***
// Load the User with its new type
return getUserById(userId);
请注意引发此异常的手册 flush():
org.hibernate.PersistentObjectException: detached entity passed to persist: com.myapp.domain.Membership
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102)
at org.hibernate.impl.SessionImpl.firePersistOnFlush(SessionImpl.java:671)
at org.hibernate.impl.SessionImpl.persistOnFlush(SessionImpl.java:663)
at org.hibernate.engine.CascadingAction$9.cascade(CascadingAction.java:346)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:319)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:265)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:242)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
at org.hibernate.engine.Cascade.cascade(Cascade.java:153)
at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:154)
at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88)
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:996)
at org.hibernate.impl.SessionImpl.executeNativeUpdate(SessionImpl.java:1185)
at org.hibernate.impl.SQLQueryImpl.executeUpdate(SQLQueryImpl.java:357)
at org.hibernate.ejb.QueryImpl.executeUpdate(QueryImpl.java:51)
at com.myapp.repository.user.JpaUserRepository.convertUserType(JpaUserRepository.java:107)
您可以看到 Membership 实体(其中 User 具有 OneToMany 集)导致了一些问题。我对幕后发生的事情了解得不够多,无法破解这个坚果。
更新 2:目前唯一可行的方法是更改 DTYPE,如上述代码所示,然后调用 entityManager.clear()强>
我不完全理解清除整个持久性上下文的后果,我希望 Session.evict() 处理正在更新的特定实体。
【问题讨论】:
标签: hibernate jpa transactions entitymanager