如何在JPA中更改实体类型?

在我的特定情况下,我正在使用区分列策略。这意味着我的JPA实现(hibernate)创建带有特殊 DTYPE 列的 users

表。此列包含实体的类名称。例如,我的 用户 表可以具有 子类。这些类名称将在

DTYPE 列中,以便EntityManager从数据库加载实体时,它知道要实例化的类的类型。 *** __

我尝试了两种转换实体类型的方法,但都觉得自己很脏:

  1. 使用本机查询对列手动执行UPDATE,以更改其值。这适用于属性约束相似的实体。
  2. 创建目标类型的新实体,进行 BeanUtils.copyProperties() 调用以移至属性上方,保存新实体,然后调用一个命名查询,该查询将新ID手动替换为旧ID,以便所有外键约束被维护。

#1的问题在于,当您手动更改此列时,JPA不知道如何刷新此实体或将其重新附加到持久性上下文。它需要一个

ID为1234,而不是一个

ID为1234它失败了。在这里,我可能会执行EntityManager.clear()并分离所有实体/清除Per。上下文,但是由于这是Service

Bean,它将为系统的所有用户清除挂起的更改。

#2的问题是,当您删除 时,您设置为Cascade =

ALL的所有属性也将被删除。这很糟糕,因为您仅尝试交换其他用户,而不删除所有扩展对象图。

:#2的问题对我来说几乎使一切都无法使用,因此我已经放弃尝试使其工作。最好的技巧无疑是第一,我在这方面取得了一些进步。关键是首先获取对基础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);

请注意引发此异常的手动 :

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)

您可以看到 具有一个OneToMany Set 的 实体引起了一些问题。我对幕后发生的事情了解得还不够多。


:到目前为止,唯一有效的方法是更改​​上述代码中所示的DTYPE,然后调用

我不完全了解清除整个持久性上下文的后果,我希望让 在要更新的特定实体上工作。

回答:

所以我终于找到了一个可行的解决方案:

放弃 以更新 DTYPE 。这主要是因为

必须在事务中运行。您可以尝试在现有事务中运行它,但这可能与您正在修改的实体的持久性上下文有关。这意味着更新 DTYPE之后, 您必须找到一种方法

实体。最简单的方法是调用

但这会导致各种副作用(请参阅JPA规范中的相关内容)。更好的解决方案是获取基础委托(在我的情况下为Hibernate )并调用

。这可能适用于简单的域图,但是我的却非常复杂。我一直无法使

与我现有的JPA批注(如 一起正常工作。我还尝试手动将域图传递给

并让每个父实体逐出其子代。由于未知原因,这也不起作用。

我被留在只有

可以工作的情况下,但是我不能接受副作用。然后,我尝试创建专门用于实体转换的单独的持久性单元。我认为我可以将

操作本地化到仅负责转换的PC。我设置了一个新的PC,一个新的对应的 ,一个新的Transaction

Manager,然后将该事务管理器手动注入到存储库中,以便在与正确的PC对应的事务中手动包装

。在这里我不得不说,我对Spring / JPA容器管理的交易还不甚了解,因为它最终成为试图获取本地/手动交易的噩梦。

可以很好地处理从Service层拉入的容器管理的事务。

在这一点上,我抛出了所有内容并创建了这个类:

@Transactional(propagation = Propagation.NOT_SUPPORTED)

public class JdbcUserConversionRepository implements UserConversionRepository {

@Resource

private UserService userService;

private JdbcTemplate jdbcTemplate;

@Override

@SuppressWarnings("unchecked")

public User convertUserType(final User user, final Class targetClass) {

// Update the DTYPE

jdbcTemplate.update("update user set user.DTYPE = ? where user.id = ?", new Object[] { targetClass.getSimpleName(), user.getId() });

// Before we try to load our converted User back into the Persistence

// Context, we need to remove them from the PC so the EntityManager

// doesn't try to load the cached one in the PC. Keep in mind that all

// of the child Entities of this User will remain in the PC. This would

// normally cause a problem when the PC is flushed, throwing a detached

// entity exception. In this specific case, we return a new User

// reference which replaces the old one. This means if we just evict the

// User, then remove all references to it, the PC will not be able to

// drill down into the children and try to persist them.

userService.evictUser(user);

// Reload the converted User into the Persistence Context

return userService.getUserById(user.getId());

}

public void setDataSource(final DataSource dataSource) {

this.jdbcTemplate = new JdbcTemplate(dataSource);

}

}

我相信此方法可以使它起作用,它包含两个重要部分:

  1. 我已经用 对其进行了标记, 这应该挂起来自Service层的容器管理的事务,并允许在PC外部进行转换。
  2. 在尝试将转换后的实体重新加载到PC之前,我使用 将当前存储在PC中的旧副本逐出 。为此的代码只是获取一个 实例并调用 。有关更多详细信息,请参见代码中的注释,但是基本上,如果我们不这样做,则对 任何调用都将尝试返回仍在PC中的缓存的Entity,除了会引发关于类型不同的错误。

尽管我的初始测试进展顺利,但此解决方案可能仍然存在一些问题。由于它们被发现,我将保持更新。

以上是 如何在JPA中更改实体类型? 的全部内容, 来源链接: utcz.com/qa/407418.html

回到顶部