2009-07-13 13 views
8

Estoy usando hibernar como una capa de persistencia. Hay 2 entidades que viven en la misma tabla que extienden una superclase con una estrategia de herencia de tabla única.Cambiar el tipo de una entidad que conserva su ID

@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 
} 

Tengo una instancia de B con id = 4. ¿Cómo cambio el tipo de esta instancia a C preservando su ID (4)?

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

El código anterior falla con

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

¿Es posible en absoluto?

+0

¿Cómo falla? – skaffman

+0

He agregado la excepción a la pregunta – artemb

+0

Solo una corazonada, pero ¿ha intentado llamar a merge() en lugar de persist()? – skaffman

Respuesta

11

Hibernate intenta hacer que la persistencia sea lo más transparente posible, lo que significa que intenta seguir los mismos principios que los objetos normales de Java.Ahora, reformulando su pregunta en Java, obtendría:

¿Cómo puedo convertir una instancia de la clase B en una instancia de clase C (incompatible)?

Y usted sabe la respuesta a eso, no puede. Puede crear una nueva instancia de C y copiar los atributos necesarios, pero B siempre ser B, nunca C. Por lo tanto, la respuesta a su pregunta original es: no se puede hacer a través de API JPA o Hibernate.

Sin embargo, a diferencia de Java convencionales, con Hibernate puede hacer trampa :-) InheritanceType.SINGLE_TABLE se asigna utilizando @DiscriminatorColumn y con el fin de convertir B en C que necesita para actualizar su valor de lo que está especificado para B en todo lo que está especificado para C. El truco es - no puedes hacerlo usando Hibernate API; tienes que hacerlo a través de SQL simple. Sin embargo, puede asignar esta instrucción de actualización como consulta SQL con nombre y ejecutarla mediante las instalaciones de Hibernate.

El algoritmo, por lo tanto, es:

  1. Expulsar B de la sesión si está allí (esto es importante)
  2. ejecutar su consulta con nombre.
  3. Carga what-is-now-known-as-C utilizando el ID de ex B.
  4. Actualizar/establecer atributos según sea necesario.
  5. Persistir C
2

En este caso, "c" es un objeto del que la sesión de hibernación no sabe nada, pero tiene una ID, por lo que se supone que el objeto ya se ha conservado. En ese contexto, persistir() no tiene sentido, y por lo tanto falla.

El javadoc para Hibernate Session.persist() (sé que no está utilizando la API de Hibernate, pero la semántica es la misma, y ​​los documentos de hibernación son mejores) dice "Hacer una instancia transitoria persistente". Si su objeto ya tiene una ID, no es transitorio. En cambio, cree que es una instancia separada (es decir, una instancia que se ha conservado, pero no está asociada a la sesión actual).

Sugiero que intente fusionar() en lugar de persistir().

+0

Gracias por explicar el motivo de la excepción, me molestaría. Merge no falla, pero le da a "c" una nueva identificación, ignorando lo que se ha establecido. – artemb

0

Usted puede utilizar su propia identificación (no generada) y hacer lo siguiente:

  1. retrive B
  2. transacción abierta
  3. eliminar B
  4. confirmar la transacción
  5. abrir una nueva transacción
  6. crear C y persistirlo
  7. Cerrar la segunda trans acción

De esta manera se borrará el id de la tabla antes de volver a insertarlo como C.

+0

Eso sería bastante difícil de implementar. Todas mis entidades usan identificadores autoincrementados. ¿Hay alguna manera de forzar el valor de una identificación generada? – artemb

0

¿Cómo distinguir entre las dos entidades en la tabla? Supongo que hay algún valor (o valores) de campo que puede cambiar para convertir a B en una C?

Puede tener un método donde cargar la superclase A y cambiar los valores distintivos y guardar. Luego, en su próxima sesión de Hibernate su B será una C.

+0

Hibernate utiliza una columna de discriminador para determinar qué clase necesita para crear instancias al cargar datos. El valor de la columna es igual al nombre simple de la clase. ¿Sugiere usar SQL simple para modificar los datos de la tabla? No veo otra manera de hacer lo que sugieres. – artemb

0

Creo que skaffman está aquí, una vez que se ha establecido el ID, no persistirá, y además porque se genera la identificación, espera que la secuencia esté en cargo de asignar el número de identificación.

¿No podría poner la identificación como @GeneratedValue? o uno de los diferentes tipos de estrategia de generador para evitar que la combinación genere un nuevo valor de secuencia, pero sospecho que sería problemático.

Cuestiones relacionadas