Как изменить тип сущности в JPA?
в моем конкретном случае я использую стратегию столбца дискриминатора. Это означает, что моя реализация JPA (Hibernate) создает пользователи таблица со специальным DTYPE
5 ответов
вам действительно нужно представлять уровень подписки в типе пользователя? Почему бы вам не добавить тип подписки в свою систему?
затем отношение будет выражено следующим образом: пользователь имеет одну подписку. Он также может быть смоделирован как отношение "один ко многим", если вы хотите сохранить историю подписок.
затем, когда вы хотите изменить уровень подписки пользователя, вместо создания нового пользователя можно создать новую подписку и присвоить его пользователь.
// When you user signs up
User.setSubscription(new FreeSubscription());
// When he upgrades to a paying account
User.setSubscription(new PayingSubscription());
действительно ли TrialUser отличается от PayingUser? Скорее всего, нет. Вам будет лучше служить агрегация вместо наследования.
данные подписки могут храниться в отдельной таблице (сопоставленной как сущность) или в таблице Users (сопоставленной как компонент).
Итак, я, наконец, понял рабочее решение:
угробили EntityManager обновление DTYPE. Это в основном потому, что запрос.executeUpdate() должны выполняться в рамках транзакции. Вы можете попробовать запустить его в существующей транзакции, но это, вероятно, связано с тем же контекстом сохранения объекта, который вы изменяете. Это означает, что после обновления DTYPE вы должны найти способ выселить() сущность. Простой способ-позвонить entityManager.clear () но это приводит к всевозможным побочным эффектам (читайте об этом в спецификации JPA). Лучшее решение-получить базовый делегат (в моем случае Hibernate сессии) и вызов сессии.выселить (пользователя). Это, вероятно, будет работать на простых графах домена, но мои были очень сложными. Я никогда не мог получить @Cascade (CascadeType.Выселить) правильная работа с существующей СПД аннотации, как @OneToOne (каскад = каскадный тип.Все). Я также попытался вручную передать мой доменный граф a сессии и каждый родительский объект выселяет своих детей. Это также не сработало по неизвестным причинам.
я остался в ситуации, когда только entityManager.clear () будет работать, но я не мог принять побочные эффекты. Затем я попытался создать отдельную единицу персистентности специально для преобразования сущностей. Я решил, что смогу локализовать the очистить() деятельность к только этому ПК на попечении преобразований. Я установил новый ПК, новый соответствующий EntityManagerFactory, новый менеджер транзакций для него и вручную вводит этот менеджер транзакций в репозиторий для ручной упаковки 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);
}
}
есть две важные части этого метода, которые, я считаю, заставляют его работать:
- я пометил его @Transactional(распространение = распространение.NOT_SUPPORTED) который следует приостановить управляемая транзакция контейнера, поступающая с уровня сервиса и позволяющая преобразованию происходить за пределами ПК.
- прежде чем пытаться перезагрузить преобразованный объект обратно в ПК, я выселяю старую копию, хранящуюся в настоящее время на ПК с приложения UserService.evictUser(пользователь);. Код для этого просто получает сессии экземпляра и вызов выселить(пользователей). См. комментарии в коде для получения более подробной информации, но в основном, если мы этого не делаем звонки в функций getUser попытается вернуть кэшированный объект все еще на ПК, за исключением того, что он выдаст ошибку о том, что тип отличается.
хотя мои начальные тесты прошли хорошо, у этого решения все еще могут быть некоторые проблемы. Я буду держать это в курсе, когда они будут раскрыты.
проблема больше связана с Java, чем что-либо еще. Вы не можете изменить тип выполнения экземпляра (во время выполнения), поэтому hibernate не предоставляет такого рода сценариев (в отличие от rails, например).
Если вы хотите выселить пользователя из сеанса, вам придется выселить связанные объекты. У вас есть несколько вариантов:
- сопоставьте объект с evict=cascade (см. сессия#выселять javadoc)
- вручную удалить все связанные с объекты (это может быть громоздким)
- очистить сеанс, таким образом, выселяя все объекты (конечно, вы потеряете локальный кэш сеанса)
У меня была аналогичная проблема в grails, и мое решение похоже на Григория решение. Я копирую все поля экземпляра, включая связанные сущности, затем удаляю старую сущность и пишу новую. Если у вас не так много отношений, это может быть легко, но если ваша модель данных сложна, вы будете лучше выключено использование описанного собственного решения sql.
вот источник, Если вам интересно:
def convertToPacient = {
withPerson(params.id) {person ->
def pacient = new Pacient()
pacient.properties = person.properties
pacient.id = null
pacient.processNumber = params.processNumber
def ap = new Appointment(params)
pacient.addToAppointments(ap);
Person.withTransaction {tx ->
if (pacient.validate() && !pacient.hasErrors()) {
//to avoid the "Found two representations of same collection" error
//pacient.attachments = new HashSet(person.attachments);
//pacient.memberships = new HashSet(person.memberships);
def groups = person?.memberships?.collect {m -> Group.get(m.group.id)}
def attachs = []
person.attachments.each {a ->
def att = new Attachment()
att.properties = a.properties
attachs << att
}
//need an in in order to add the person to a group
person.delete(flush: true)
pacient.save(flush: true)
groups.each {g -> pacient.addToGroup(g)};
attachs.each {a -> pacient.addToAttachments(a)}
//pacient.attachments.each {att -> att.id = null; att.version = null; att.person = pacient};
if (!pacient.save()) {
tx.setRollbackOnly()
return
}
}
}
проблема больше связана с Java больше всего на свете. Ты не можешь изменить ... типом времени выполнения экземпляра (в runtime), поэтому hibernate не предусмотреть такие сценарии (в отличие от например рельсы).
я столкнулся с этим сообщением, потому что у меня (или думал, что у меня) есть аналогичная проблема. Я пришел к выводу, что проблема заключается не в механике изменения типов в технологии, а в том, что развитие типов в целом не так просто или простой. Проблема в том, что типы довольно часто имеют различные атрибуты. Если вы не сопоставляете разные типы только для поведенческих различий, неясно, что делать с дополнительной информацией. По этой причине имеет смысл переносить типы на основе построения новых объектов.
проблема не связана с Java. это связано с теорией типов в целом и более конкретно объектно-реляционным отображением здесь. Если ваш язык поддерживает ввод текста, я хотел бы услышать как вы можете автоматически изменить тип объекта во время выполнения и волшебным образом сделать что-то умное с оставшейся информацией. Пока мы распространяем FUD: остерегайтесь, от кого вы принимаете советы: Rails-это фреймворк, Ruby-это язык.
когда вы говорите, вы, наверное, искажать проблему. На мой взгляд, вы действительно пытаетесь построить экземпляр одного класса на основе экземпляра другого класса, например:
public PayingUser(TrialUser theUser) {
...
}
затем вы можете удалить старого пробного пользователя и сохранить нового платежного пользователя.