Изменение типа объекта с сохранением его идентификатора - PullRequest
8 голосов
/ 13 июля 2009

Я использую hibernate как постоянный слой. Есть две сущности, которые живут в одной таблице, расширяя один суперкласс с помощью стратегии наследования одной таблицы.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class A {
    @Id
    @GeneratedValue
    protected Long id;

    // some common fields for B and C
}


@Entity
public class B extends A {
    // B-specific fields
}

@Entity
public class C extends A {
    // C-specific fields
}

У меня есть экземпляр B с id = 4. Как изменить тип этого экземпляра на C, сохранив его идентификатор (4)?

B b = em.find(B.class, 4L);
C c = convertToC(b);
c.setId(b.getId());
em.remove(b);
em.persist(c);

Приведенный выше код завершается ошибкой с

org.hibernate.PersistentObjectException: detached entity passed to persist: C

Возможно ли это вообще?

Ответы [ 5 ]

11 голосов
/ 11 сентября 2009

Hibernate пытается сделать постоянство настолько прозрачным, насколько это возможно, что означает, что он пытается следовать тем же принципам, что и обычные объекты Java. Теперь, перефразируя ваш вопрос на Java, вы получите:

Как я могу преобразовать экземпляр класса B в экземпляр (несовместимого) класса C?

И вы знаете ответ на этот вопрос - вы не можете. Вы можете создать новый экземпляр C и скопировать необходимые атрибуты, но B будет всегда быть B, а не C. Таким образом, ответ на ваш оригинальный вопрос - это нельзя сделать с помощью JPA или Hibernate API.

Однако, в отличие от простой Java, в Hibernate вы можете обмануть :-) InheritanceType.SINGLE_TABLE отображается с помощью @DiscriminatorColumn, и для преобразования B в C вам необходимо обновить его значение из того, что указано для B, во все, что указано для C Хитрость в том, что вы не можете сделать это с помощью Hibernate API; вам нужно сделать это с помощью простого SQL. Однако вы можете отобразить этот оператор обновления как именованный запрос SQL и выполнить его с помощью средств Hibernate.

Таким образом, алгоритм имеет вид:

  1. Выселить B из сессии, если она есть (это важно)
  2. Выполните ваш именованный запрос.
  3. Загрузить то, что сейчас известно как C, используя прежний идентификатор B.
  4. Обновлять / устанавливать атрибуты по мере необходимости.
  5. Persist C
2 голосов
/ 13 июля 2009

В этом случае «c» - это объект, о котором сеанс гибернации ничего не знает, но у него есть идентификатор, поэтому он предполагает, что объект уже был сохранен. В этом контексте, persist () не имеет смысла, и поэтому он терпит неудачу.

Javadoc для Hibernate Session.persist () (я знаю, что вы не используете Hibernate API, но семантика одна и та же, а документы гибернации лучше) говорит "Сделать временный экземпляр постоянным". Если у вашего объекта уже есть идентификатор, он не является временным. Вместо этого он считает, что это отдельный экземпляр (то есть экземпляр, который был сохранен, но не связан с текущим сеансом).

Я предлагаю вам попробовать merge () вместо persist ().

0 голосов
/ 14 июля 2009

Я думаю, что skaffman прямо здесь, после того, как идентификатор был установлен, он не будет сохраняться, и далее, поскольку идентификатор генерируется, он ожидает, что последовательность будет отвечать за присвоение номера идентификатора.

Вы не могли бы поставить идентификатор как @GeneratedValue? или один из различных типов стратегии генератора, возможно, чтобы избежать слияния, генерирующего новое значение последовательности, но я подозреваю, что это будет проблематично.

0 голосов
/ 13 июля 2009

Как вы различаете две сущности в таблице? Я предполагаю, что есть какое-то значение поля (или значения), которое вы можете изменить, чтобы превратить B в C?

У вас может быть метод, в котором вы загружаете суперкласс A, меняете отличительные значения и сохраняете. Тогда на следующей сессии Hibernate ваш B будет C.

0 голосов
/ 13 июля 2009

Вы можете использовать свой собственный идентификатор (не генерируется) и делать следующее:

  1. Retrive B
  2. открытая транзакция
  3. удалить B
  4. совершить транзакцию
  5. открыть новую транзакцию
  6. создать C и сохранить его
  7. Закрыть вторую транзакцию

Таким образом вы удалите идентификатор из таблицы, прежде чем вставлять его как C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...