所以我终于找到了一个可行的解决方案:
放弃 EntityManager 以更新 DTYPE 。这主要是因为 Query.executeUpdate()
必须在事务中运行。您可以尝试在现有事务中运行它,但这可能与您正在修改的实体的持久性上下文有关。这意味着更新 DTYPE之后, 您必须找到一种方法
evict() 实体。最简单的方法是调用 entityManager.clear(),
但这会导致各种副作用(请参阅JPA规范中的相关内容)。更好的解决方案是获取基础委托(在我的情况下为Hibernate Session )并调用
Session.evict(user) 。这可能适用于简单的域图,但是我的却非常复杂。我一直无法使
@Cascade(CascadeType.EVICT) 与我现有的JPA批注(如 @OneToOne(cascade =
CascadeType.ALL) ) 一起正常工作。我还尝试手动将域图传递给 会话,
并让每个父实体逐出其子代。由于未知原因,这也不起作用。
我被留在只有 entityManager.clear()
可以工作的情况下,但是我不能接受副作用。然后,我尝试创建专门用于实体转换的单独的持久性单元。我认为我可以将 clear()
操作本地化到仅负责转换的PC。我设置了一个新的PC,一个新的对应的 EntityManagerFactory ,一个新的Transaction
Manager,然后将该事务管理器手动注入到存储库中,以便在与正确的PC对应的事务中手动包装 executeUpdate()
。在这里我不得不说,我对Spring / JPA容器管理的交易还不甚了解,因为它最终成为试图获取本地/手动交易的噩梦。
executeUpdate() 可以很好地处理从Service层拉入的容器管理的事务。
在这一点上,我抛出了所有内容并创建了这个类:
@Transactional(propagation = Propagation.NOT_SUPPORTED)public class JdbcUserConversionRepository implements UserConversionRepository {@Resourceprivate 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); }}我相信此方法可以使它起作用,它包含两个重要部分:
- 我已经用 @Transactional(propagation = Propagation.NOT_SUPPORTED) 对其进行了标记, 这应该挂起来自Service层的容器管理的事务,并允许在PC外部进行转换。
- 在尝试将转换后的实体重新加载到PC之前,我使用 userService.evictUser(user) 将当前存储在PC中的旧副本逐出 。 。为此的代码只是获取一个 Session 实例并调用 evict(user) 。有关更多详细信息,请参见代码中的注释,但是基本上,如果我们不这样做,则对 getUser的 任何调用都将尝试返回仍在PC中的缓存的Entity,除了会引发关于类型不同的错误。
尽管我的初始测试进展顺利,但此解决方案可能仍然存在一些问题。由于它们被发现,我将保持更新。



