Pregunta sobre many-to-one, hibernate-cascade, jpa, java, hibernate – IllegalStateException con Hibernate 4 y ManyToOne en cascada

23

Tengo esas dos clases

Objeto MyItem:

<code>@Entity
public class MyItem implements Serializable {

    @Id
    private Integer id;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Component defaultComponent;
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Component masterComponent;

    //default constructor, getter, setter, equals and hashCode
}
</code>

Objeto componente:

<code>@Entity
public class Component implements Serializable {

    @Id
    private String name;

    //again, default constructor, getter, setter, equals and hashCode
}
</code>

Y estoy tratando de persistir aquellos con el siguiente código:

<code>public class Test {

    public static void main(String[] args) {
        Component c1 = new Component();
        c1.setName("comp");
        Component c2 = new Component();
        c2.setName("comp");
        System.out.println(c1.equals(c2)); //TRUE

        MyItem item = new MyItem();
        item.setId(5);
        item.setDefaultComponent(c1);
        item.setMasterComponent(c2);

        ItemDAO itemDAO = new ItemDAO();
        itemDAO.merge(item);
    }
}
</code>

Si bien esto funciona bien con Hibernate 3.6, Hibernate 4.1.3 lanza

<code>Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
        at org.hibernate.event.internal.EventCache.put(EventCache.java:184)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:285)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
        at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
        at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:423)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:213)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:282)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:888)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:892)
        at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:874)
        at sandbox.h4bug.Test$GenericDAO.merge(Test.java:79)
        at sandbox.h4bug.Test.main(Test.java:25)
</code>

El backend de la base de datos es h2 (pero lo mismo sucede con hsqldb o derby). ¿Qué estoy haciendo mal?

Tu respuesta

9   la respuesta
0
El problema sigue ahí con Jboss AS 7.2. Magnilex
27

El método de combinación atraviesa el gráfico del objeto que desea almacenar, y para cada objeto en este gráfico lo carga desde la base de datos, por lo que tiene un par de (entidad persistente, entidad separada) para cada objeto en el gráfico, donde entidad separada es la entidad que se va a almacenar, y la entidad persistente se obtiene de la base de datos. (En el método, así como en el mensaje de error, la entidad persistente se conoce como "copia"). Luego estos pares se colocan en dos mapas, uno con la entidad persistente como clave y la entidad separada como valor, y uno con la entidad separada como clave y la entidad persistente como valor.

Para cada uno de dichos pares de entidades, verifica estos mapas para ver si la entidad persistente se asigna a la misma entidad separada que antes (si ya se ha visitado) y viceversa. Este problema se produce cuando obtiene un par de entidades en las que realizar una obtención con la entidad persistente devuelve un valor, pero una obtención de la otra asignación, con la entidad separada devuelve un valor nulo, lo que significa que ya ha vinculado la entidad persistente con una separación entidad con un código hash diferente (básicamente el identificador de objeto si no ha anulado el método hashcode).

TL; DR, tiene varios objetos con diferentes identificadores de objeto / código hash, pero con el mismo identificador de persistencia (por lo tanto, hace referencia a la misma entidad persistente). Aparentemente, esto ya no está permitido en las versiones más recientes de Hibernate4 (4.1.3.Final y superior a lo que pude ver).

El mensaje de error no es muy bueno, lo que realmente debería decir es algo como:

A persistent entity has already been assigned to a different detached entity

o

Multiple detached objects corresponding to the same persistent entity

El problema aún existe con la versión "4.1.10 final". Sin embargo, volver a la versión "4.1.2 Final" funciona. Zaki
Si no está serializando / deserializando, entonces la excepción podría ser una indicación de que el código no está funcionando como debería. Así que descubra qué objeto persistente está siendo representado por múltiples objetos (java) y por qué. Si es por una razón aceptable, y no puede ser evadido de una manera fácil, utilice la degradación como último recurso. Tobb
No estoy seguro, pero por lo que entiendo de su descripción, parece que es incluso "más válido", lo que significa que no puede evitar el error agregando sus propios iguales / código de hash. Ahora, es necesario que haya un 1-1 entre los identificadores de objeto y los identificadores de base de datos en el gráfico de objeto para que se fusionen y no se produzca tal excepción. Tobb
En mi caso, el problema era que tenía un gráfico de objetos que se había separado del administrador de entidades, se serializaba, se deserializaba y se fusionaba nuevamente con el administrador de entidades. Pero la serialización / deserialización hizo lo que solía ser referencias a las mismas referencias de objetos a diferentes objetos. No estoy seguro si tiene el mismo escenario, pero la solución podría ser asegurarse de que cada objeto persistente (identificado por el id de la base de datos) sea el mismo objeto (el mismo id del objeto). En mi caso, el dominio era demasiado complejo para tal solución, por lo que degradar la versión de Hibernate se convirtió en la solución más segura. Tobb
4

unidireccional obidireccional? Si es bidireccional asegúrate de no tenerCascade.MERGE Llamadas volviendo al artículo.

Básicamente, la versión más reciente de Hibernate tiene un mapa de entidad que contiene una lista de todas las cosas que deben fusionarse en función de la llamada a fusionar () que llamará fusionar y luego se moverá a la siguiente, pero mantendrá las cosas en el mapa. arrojará el error que indica sobre "Ya se ha asignado una copia de entidad a una entidad diferente" cuando encuentra un elemento que ya se ha tratado. Hemos encontrado en nuestra aplicación cuando localizamos estas combinaciones "ascendentes" en el gráfico de objetos, es decir. En los enlaces bidireccionales, se corrigió la llamada de combinación.

0

solo lo resolví. Si bien las respuestas anteriores pueden resolver el problema, no estoy de acuerdo con algunas de ellas, especialmente con la modificación de los métodos implementados de equlas () y hashcode (). Sin embargo, siento que mi respuesta refuerza las respuestas de @Tobb y @Supun s.

En mi lado de muchos (lado de niño) tuve

 @OneToMany(mappedBy = "authorID", cascade =CascadeType.ALL, fetch=FetchType.EAGER)
 private Colllection books;

Y en mi lado (lado padre)

 @ManyToOne(cascade =CascadeType.ALL)
 private AuthorID authorID;

Después de leer la excelente respuesta que proporcionó @Tobb y pensar un poco, me di cuenta de que las anotaciones no tenían sentido. De la forma en que lo entiendo (en mi caso) fusioné () el objeto Autor y fusioné () el Objeto libro. Pero como la colección de libros es un componente del objeto Autor, estaba intentando guardarlo dos veces. Mi solución fue cambiar los tipos de cascada a:

  @OneToMany(mappedBy = "authorID", cascade =CascadeType.PERSIST, fetch=FetchType.EAGER)
  private Collection bookCollection;

y

 @ManyToOne(cascade =CascadeType.MERGE)
 private AuthorID authorID;

Para resumir una larga historia, Persista el objeto primario y fusione el objeto secundario.

Espero que esto ayude / tenga sentido.

5

revisa tu método equals (). Lo más probable es que esté mal implementado.

Edición: he verificado que una operación de combinación no funcionará si no implementas los métodos equals () y hashCode () de tu Entidad correctamente.

Debe seguir estas pautas para implementar equals () y hashCode ():

http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode

"Se recomienda que implemente equals () y hashCode () usando la igualdad de la clave de negocios. Igualdad de la clave de negocios significa que el método equals () compara solo las propiedades que forman la clave de negocios. Es una clave que identificaría nuestra instancia en el mundo real (una clave de candidato natural) "

Eso significa que: ¡NO debe usar su ID como parte de su implementación equals ()!

Más información:blog.andrewbeacock.com/2008/08/… Ricardo Arguello
0

¿por qué estás creando dos objetos con el mismo ID? Puedes usar el objeto c1 en todo el código.

Si eso es solo un ejemplo y creas el objeto c2 en otra parte del código, entonces no deberías crear un nuevo objeto sino cargarlo desde la base de datos:

c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted
2

objeto que tiene dos copias de un objeto hijo, que se corrigió en la entidad desde:

@OneToOne(cascade = CascadeType.MERGE)
private User reporter;
@OneToOne(cascade = CascadeType.MERGE)
private User assignedto;

para sólo,

@OneToOne
private User reporter;
@OneToOne
private User assignedto;

aunque no sé la razón

0

@GeneratedValue anotación bajo@Id en la clase de componentes. de lo contrario, dos instancias diferentes podrían obtener el mismo ID y colisionar.

Parece que les estás dando la misma identificación.

    Component c1 = new Component();
    c1.setName("comp");
    Component c2 = new Component();
    c2.setName("comp");

Eso podría resolver su problema.

@RichardPena, si desea que estas dos instancias representen la misma entidad de base de datos, ¿por qué no usar solo una instancia? Ido.Co
Desafortunadamente, ambos identificadores no se generan en la base de datos, sino que se establecen explícitamente. Clayton Louden
Sí, esa es la idea detrás de esto. Ambos componentes obtienen el mismo ID y son iguales (consulte la declaración de iguales más arriba). Así que la cascada debería encargarse de esto, ¿verdad? Incluso puedes intentar usar la misma referencia (por ejemplo, c1) para ambas variables (defaultComponent y masterComponent), ya que son iguales de todos modos. Clayton Louden
Vaya, me refiero a la clase de componente. les estas dando la misma id? Ido.Co
¿Por qué intenta asignar dos instancias diferentes que representan la misma entidad de base de datos a una clase? Hubiera sido mejor usar la misma instancia. Creo que estás abusando del campo DB id @. Ido.Co
0

todas las entidades en el gráfico de objetos deben ser únicas. Entonces, la mejor solución (¿o es una solución alternativa?) Es eliminar la cascada en MyItem to Component. Y fusione el Componente por separado si es realmente necesario: apostaría a que, en el 95% de los casos, el Componente no debería fusionarse de acuerdo con la lógica empresarial.

Por otro lado, realmente me interesó saber los pensamientos reales detrás de esa restricción.

Preguntas relacionadas