Error de hibernación: un objeto diferente con el mismo valor de identificador ya estaba asociado con la sesión

94

Básicamente, tengo algunos objetos en esta configuración (el modelo de datos real es un poco más complejo):

  • A tiene una relación de muchos a muchos con B. (B tiene inverse="true")
  • B tiene una relación de muchos a uno con C. (me he cascadefijado en "save-update")
  • C es una especie de tabla de tipo / categoría.

Además, probablemente debería mencionar que las claves primarias son generadas por la base de datos al guardar.

Con mis datos, a veces me encuentro con problemas en los que A tiene un conjunto de diferentes objetos B, y estos objetos B se refieren al mismo objeto C.

Cuando llamo session.saveOrUpdate(myAObject), me sale un error de hibernación diciendo: "a different object with the same identifier value was already associated with the session: C". Sé que Hibernate no puede insertar / actualizar / eliminar el mismo objeto dos veces en la misma sesión, pero ¿hay alguna forma de evitar esto? Esta no parece ser una situación tan poco común.

Durante mi investigación de este problema, he visto a personas sugerir el uso de session.merge(), pero cuando lo hago, cualquier objeto "en conflicto" se inserta en la base de datos como objetos en blanco con todos los valores establecidos en nulos. Claramente eso no es lo que queremos.

[Editar] Otra cosa que olvidé mencionar es que (por razones arquitectónicas fuera de mi control), cada lectura o escritura debe realizarse en una sesión separada.

Juan
fuente
Mira si esta respuesta te ayuda ...
joaonlima

Respuestas:

98

Lo más probable es que se deba a que los objetos B no se refieren a la misma instancia de objeto Java C. Se refieren a la misma fila en la base de datos (es decir, la misma clave primaria) pero son copias diferentes de ella.

Entonces, lo que está sucediendo es que la sesión de Hibernate, que administra las entidades, estaría realizando un seguimiento de qué objeto Java corresponde a la fila con la misma clave primaria.

Una opción sería asegurarse de que las Entidades de los objetos B que hacen referencia a la misma fila en realidad se refieren a la misma instancia de objeto de C. Alternativamente, desactive la cascada para esa variable miembro. De esta forma, cuando B persiste, C no lo es. Sin embargo, tendrá que guardar C manualmente por separado. Si C es una tabla de tipo / categoría, entonces probablemente tenga sentido que sea así.

jbx
fuente
3
Gracias jbx. Como dijiste, resulta que los objetos B se refieren a múltiples instancias C en la memoria. Básicamente, lo que está sucediendo es que una parte de mi programa lee en C y lo adjunta a B. Otra parte carga una B diferente con la misma C de la base de datos. Ambos se adjuntan a A, lo que desencadena el error al guardar. Establecí la <pre> cascada </pre> para la relación B-> C en "<pre> none </pre>", pero sigo recibiendo el mismo error. En las relaciones de varios a uno o de uno a varios, ¿hay alguna manera de decirle a Hibernate que solo cambie la clave externa y no se preocupe por el resto?
John
1
¿Tiene la clave principal de C alguna estrategia de generación de ID? ¿Como un generador de secuencias o algo similar?
jbx
Sí, cada uno tiene su propia secuencia en la base de datos. Como mencionaste, la cascada resultó ser el problema. Desactivamos la cascada para las tablas de tipos, y para las demás usamos "merge" en cascada, lo que nos permitió llamar a merge () sin crear todas esas filas nulas. He marcado tu respuesta en consecuencia, ¡gracias!
John
14
He usado merge () en lugar de saveOrUpdate () y BOOM! funciona :)
Lahiru Ruhunage
29

Simplemente configure cascada en MERGE, eso debería funcionar.

andy.codes
fuente
13

Solo necesitas hacer una cosa. Ejecute session_object.clear()y luego guarde el nuevo objeto. Esto borrará la sesión (con el nombre apropiado) y eliminará el objeto duplicado ofensivo de su sesión.

dsk
fuente
10

Estoy de acuerdo con @Hemant Kumar, muchas gracias. Según su solución, resolví mi problema.

Por ejemplo:

@Test
public void testSavePerson() {
    try (Session session = sessionFactory.openSession()) {
        Transaction tx = session.beginTransaction();
        Person person1 = new Person();
        Person person2 = new Person();
        person1.setName("222");
        person2.setName("111");
        session.save(person1);
        session.save(person2);
        tx.commit();
    }
}

Person.java

public class Person {
    private int id;
    private String name;

    @Id
    @Column(name = "id")
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Este código siempre comete errores en mi aplicación:, A different object with the same identifier value was already associated with the sessionluego descubrí que olvidé aumentar automáticamente mi clave principal.

Mi solución es agregar este código en su clave principal:

@GeneratedValue(strategy = GenerationType.AUTO)
Azul hielo
fuente
6

Esto significa que está intentando guardar varias filas en su tabla con la referencia al mismo objeto.

verifique la propiedad de identificación de su clase de entidad.

@Id
private Integer id;

a

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(unique = true, nullable = false)
private Integer id;
rex roy
fuente
1
no es el caso con la pregunta según la descripción dada
Sudip Bhandari
5

Transfiera la tarea de asignar el ID de objeto de Hibernate a la base de datos usando:

<generator class="native"/>

Esto resolvió mi problema.

usuario2845946
fuente
3

Agregue la anotación @GeneratedValue al bean que está insertando.

Hemant Kumar
fuente
¡Gracias! Este era exactamente mi problema
DriLLFreAK100
3

Una forma de resolver el problema anterior será anular el hashcode().
También vacíe la sesión de hibernación antes y después de guardar.

getHibernateTemplate().flush();

Establecer explícitamente el objeto separado en nulltambién ayuda.

Waqar
fuente
2

Acabo de encontrar este mensaje pero en código c #. No estoy seguro de si es relevante (aunque exactamente el mismo mensaje de error).

Estaba depurando el código con puntos de interrupción y expandí algunas colecciones a través de miembros privados mientras el depurador estaba en un punto de interrupción. Habiendo vuelto a ejecutar el código sin buscar en las estructuras, el mensaje de error desapareció. Parece que el acto de buscar en colecciones privadas con carga diferida ha hecho que NHibernate cargue cosas que se suponía que no debían cargarse en ese momento (porque estaban en miembros privados).

El código en sí está envuelto en una transacción bastante complicada que puede actualizar una gran cantidad de registros y muchas dependencias como parte de esa transacción (proceso de importación).

Con suerte, una pista para cualquier otra persona que se encuentre con el problema.

Ales Potocnik Hahonina
fuente
2

Busque el atributo "Cascade" en Hibernate y elimínelo. Cuando establece "Cascade" disponible, llamará a otras operaciones (guardar, actualizar y eliminar) en otras entidades que tienen relación con clases relacionadas. Entonces se sucederá el mismo valor de identidades. Funcionó conmigo.

Nguyen Vu Quang
fuente
1

Tuve este error unos días después y dediqué demasiado tiempo a solucionarlo.

 public boolean save(OrderHeader header) {
    Session session = sessionFactory.openSession();


    Transaction transaction = session.beginTransaction();

    try {
        session.save(header);

        for (OrderDetail detail : header.getDetails()) {
            session.save(detail);
        }

        transaction.commit();
        session.close();

        return true;
    } catch (HibernateException exception) {

        exception.printStackTrace();
        transaction.rollback();
        return false;
    }
}

Antes de recibir este error, no mencioné el tipo de generación de ID en el objeto OrderDetil. cuando sin generar la identificación de Orderdetails, mantiene la Id como 0 para cada objeto de OrderDetail. esto es lo que explicó #jbx. Sí, es la mejor respuesta. este ejemplo de cómo sucede.

Buddhi
fuente
1

Intente colocar el código de su consulta antes. Eso soluciona mi problema. por ejemplo, cambie esto:

query1 
query2 - get the error 
update

a esto:

query2
query1
update
juldeh
fuente
0

es posible que no esté configurando el identificador del objeto antes de llamar a la consulta de actualización.

Fawad Khaliq
fuente
3
Si no lo fuera, no tendría este problema. El problema es que tiene dos objetos con el mismo identificador.
aalku
0

Me encontré con el problema porque la generación de la clave principal es incorrecta, cuando inserto una fila como esta:

public void addTerminal(String typeOfDevice,Map<Byte,Integer> map) {
        // TODO Auto-generated method stub
        try {
            Set<Byte> keySet = map.keySet();
            for (Byte byte1 : keySet) {
                Device device=new Device();
                device.setNumDevice(DeviceCount.map.get(byte1));
                device.setTimestamp(System.currentTimeMillis());
                device.setTypeDevice(byte1);
                this.getHibernateTemplate().save(device);
            }
            System.out.println("hah");
        }catch (Exception e) {
            // TODO: handle exception
            logger.warn("wrong");
            logger.warn(e.getStackTrace()+e.getMessage());
        }
}

Cambio la clase del generador de id a la identidad

<id name="id" type="int">
    <column name="id" />
    <generator class="identity"  />
 </id>
张云 风
fuente
0

En mi caso, solo flush () no funcionó. Tuve que usar un clear () después de flush ().

public Object merge(final Object detachedInstance)
    {
        this.getHibernateTemplate().flush();
        this.getHibernateTemplate().clear();
        try
        {
            this.getHibernateTemplate().evict(detachedInstance);
        }
}
shubhranshu
fuente
0

si usa EntityRepository, use saveAndFlush en lugar de guardar

Jayen Chondigara
fuente
0

Si dejaba abierta una pestaña de expresiones en mi IDE que estaba haciendo una llamada de hibernación al objeto que causaba esta excepción. Estaba intentando eliminar este mismo objeto. También tuve un punto de interrupción en la llamada de eliminación que parece ser necesario para que ocurra este error. Simplemente hacer que otra pestaña de expresiones sea la pestaña frontal o cambiar la configuración para que el ide no se detenga en los puntos de interrupción resolvió este problema.

Aaron Byrnes
fuente
0

Asegúrese de que su entidad tenga el mismo tipo de generación con todas las entidades asignadas

Ejemplo: UserRole

public class UserRole extends AbstractDomain {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

@Enumerated(EnumType.STRING)
private CommonStatus status;

private String roleCode;

private Long level;

@Column(columnDefinition = "integer default 0")
private Integer subRoleCount;

private String modification;

@ManyToOne(fetch = FetchType.LAZY)
private TypeOfUsers licenseType;

}

Módulo:

public class Modules implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

}

Entidad principal con mapeo

public class RoleModules implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private UserRole role;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private Modules modules;

@Type(type = "yes_no")
private boolean isPrimaryModule;

public boolean getIsPrimaryModule() {
    return isPrimaryModule;
}

}

Sandeep Patel
fuente
0

Además de todas las respuestas anteriores, una posible solución a este problema en un proyecto a gran escala, si usa un objeto de valor para sus clases, no establece el atributo id en la clase VO Transformer.

George Michael
fuente
0

solo confirma la transacción actual.

currentSession.getTransaction().commit();

ahora puede comenzar otra transacción y hacer cualquier cosa en la entidad

Mahdi
fuente
0

Otro caso en el que se puede generar el mismo mensaje de error, personalizado allocationSize:

@Id
@Column(name = "idpar")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "paramsSequence")
@SequenceGenerator(name = "paramsSequence", sequenceName = "par_idpar_seq", allocationSize = 20)
private Long id;

sin emparejar

alter sequence par_idpar_seq increment 20;

puede causar la validación de la restricción durante la inserción (que es fácil de entender) o este "un objeto diferente con el mismo valor de identificador ya estaba asociado con la sesión" - este caso fue menos obvio.

user158037
fuente