Rob*_*ell 10 hibernate jpa transactions entitymanager
在我的具体案例中,我正在使用一个鉴别器列策略.这意味着我的JPA实现(Hibernate)创建了一个带有特殊DTYPE列的用户表.此列包含实体的类名.例如,我的users表可以包含TrialUser和PayingUser的子类.这些类名称将位于DTYPE列中,以便当EntityManager从数据库加载实体时,它知道要实例化的类的类型.
我尝试过两种转换实体类型的方法,两种方式都像脏黑客一样:
#1的问题是当您手动更改此列时,JPA不知道如何刷新/重新附加此实体到持久性上下文.它需要一个TrialUser ID为1234,而不是一个PayingUser ID为1234它失败了.在这里,我可以做一个EntityManager.clear()并分离所有实体/清除Per.上下文,但由于这是一个Service bean,它将擦除系统所有用户的挂起更改.
#2的问题在于,当您删除TrialUser时,您设置为Cascade = ALL的所有属性也将被删除.这很糟糕,因为您只是尝试交换其他用户,而不是删除所有扩展对象图.
更新1:#2的问题使我几乎无法使用,所以我放弃了尝试让它工作.更优雅的黑客肯定是#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);
Run Code Online (Sandbox Code Playgroud)
注意手动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)
Run Code Online (Sandbox Code Playgroud)
您可以看到,其中User具有OneToMany集的Membership实体正在导致一些问题.我不知道幕后发生了什么事情来破解这个坚果.
更新2:到目前为止唯一有效的方法是更改DTYPE,如上面的代码所示,然后调用 entityManager.clear()
我并不完全理解清除整个持久化上下文的后果,我本来希望让Session.evict()处理正在更新的特定实体.
所以我终于找到了一个可行的解决方案:
放弃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和一个新的事务管理器,并手动将该事务管理器注入到存储库中,以便在与正确的 PC 对应的事务中手动包装executeUpdate() 。在这里,我不得不说,我对 Spring/JPA 容器管理事务了解不够,因为试图让executeUpdate()的本地/手动事务与容器管理事务很好地配合,这最终成为一场噩梦从服务层。
此时我扔掉了所有东西并创建了这个类:
@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);
}
}
Run Code Online (Sandbox Code Playgroud)
我认为该方法有两个重要部分使其发挥作用:
虽然我最初的测试进展顺利,但这个解决方案可能仍然存在一些问题。当他们被发现时,我会及时更新。
| 归档时间: |
|
| 查看次数: |
11410 次 |
| 最近记录: |