В моем конкретном случае я использую стратегию столбца дискриминатора. Это означает, что моя реализация JPA (Hibernate) создает таблицу users со специальным столбцом DTYPE . Этот столбец содержит имя класса объекта. Например, моя таблица users может иметь подклассы TrialUser и PayingUser . Эти имена классов будут находиться в столбце DTYPE , поэтому, когда EntityManager загружает объект из базы данных, он знает, какой тип класса требуется создать.
Я пробовал два способа преобразования типов сущностей, и оба чувствовали себя грязными хаки:
- Используйте собственный запрос, чтобы вручную выполнить ОБНОВЛЕНИЕ для столбца, изменив его значение. Это работает для сущностей, ограничения свойств которых аналогичны.
- Создайте новую сущность целевого типа, выполните BeanUtils.copyProperties () , чтобы переместить свойства, сохранить новую сущность, затем вызвать именованный запрос, который вручную заменяет новый Id на старый идентификатор, чтобы все ограничения внешнего ключа сохранялись.
Проблема с # 1 состоит в том, что когда вы вручную изменяете этот столбец, JPA не знает, как обновить / повторно присоединить этот объект к контексту устойчивости. Ожидается TrialUser с идентификатором 1234, а не PayingUser с идентификатором 1234. Сбой. Здесь я, вероятно, мог бы сделать EntityManager.clear () и отсоединить все Entities / очистить Per. Контекст, но поскольку это служебный компонент, он будет уничтожать ожидающие изменения для всех пользователей системы.
Проблема с # 2 заключается в том, что при удалении TrialUser все свойства, которые вы установили в Cascade = ALL, также будут удалены. Это плохо, потому что вы пытаетесь поменять местами другого пользователя, а не удалить весь расширенный граф объектов.
Обновление 1 : проблемы # 2 сделали его практически бесполезным для меня, поэтому я перестал пытаться заставить его работать. Более элегантный из хаков, безусловно, № 1, и я добился определенного прогресса в этом отношении. Главное - сначала получить ссылку на базовую сессию Hibernate (если вы используете 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)
Вы можете видеть, что сущность Членство , из которых Пользователь имеет Набор OneToMany, вызывает некоторые проблемы. Я не знаю достаточно о том, что происходит за кулисами, чтобы взломать этот орех.
Обновление 2 : единственное, что работает до сих пор, - это измените DTYPE, как показано в приведенном выше коде, а затем вызовите entityManager.clear ()
Я не совсем понимаю последствия очистки всего контекста персистентности, и мне бы хотелось, чтобы Session.evict () работала над обновлением конкретной сущности.