Error en Hibernate Detached Entity Passed to Persist

detached entity passed to persist

detached entity passed to persist


En este artículo vamos a tratar el error en Hibernate Detached Entity Passed to Persist, el cual es un error que sucede cuando se intenta salvar una entidad considerada detached.

Vamos a comenzar entendiendo que son las detached entities.

A partir de Spring Boot 3 y Spring Framework 6 el error: org.hibernate.PersistentObjectException: detached entity passed to persist pasa a ser de la siguiente manera org.hibernate.PersistentObjectException` to JPA `PersistenceException` : detached entity passed to persist

¿Qué es una Detached Entity?

En el ciclo de vida de las entidades en Hibernate pasamos por 4 estados, transient, managed, detached y deleted.

Un detached Entity es uno de los estados de Hibernate. Un detached entity es un POJO cuyo valor se corresponde con una fila de la base de datos.

Una entidad pasa a ser detached cuanso se ha cerrado la sesión utilizada para cargarla o cuando llamamos a Session.clear() o Session.evict().

¿Por qué se obtiene la excepción Detached Entity Passed to Persist?

La excepción «org.hibernate.PersistentObjectException: detached entity passed to persist» o , «org.hibernate.PersistentObjectException` to JPA `PersistenceException` : detached entity passed to persist» ocurre cuando se ha cerrado la sesión utilizada para cargar la entidad o se ha utilizado Session.clear() o Session.evict().

Vamos a ver como podemos provocar la excepción org.hibernate.PersistentObjectException: detached entity passed to persist partiendo de una entidad DepartmentEntity.

@Getter
@Setter
@Entity
public class SingleDepartment {

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

  @Column
  private String name;


}

Vamos crear un test basándonos en la entidad anterior para provocar la excepción de Hibernate: org.hibernate.PersistentObjectException: detached entity passed to persist:

  @Test
  public void given_department_when_detach_a_single_to_change_name_then_exception() {

    singleDepartment = new SingleDepartment();
    singleDepartment.setName("Accounts");
    session.persist(singleDepartment);
    session.evict(singleDepartment);

    singleDepartment.setName("Accounts exception");

    session.getTransaction().commit();

    assertThatThrownBy(() -> session.persist(singleDepartment)).isInstanceOf(
        PersistenceException.class).hasMessageContaining(
        "org.hibernate.PersistentObjectException` to JPA `PersistenceException` : detached entity passed to persist: com.refactorizando.example.detachentity.entity.SingleDepartment");

  }

¿Cómo solucionar la excepción Detached Entity Passed to Persist?

Para solucionar la excepción provocada por Hibernate detached entity passed to persist, tenemos que tener claro en que estado se encuentra nuestra entidad y utilizar el tipo de Cascade o método correcto.

Para solucionarlo podemos usar las siguientes opciones que pertenecen a JPA:

  • Merge
  • Save: A partir de la versión 3 de Spring Boot se encuentra deprecado.
  • SaveOrUpdate: A partir de la versión 3 de Spring Boot se encuentra deprecado.
  @Test
  public void given_department_when_detach_a_single_entity_with_merge_then_saved() {

    singleDepartment = new SingleDepartment();

    singleDepartment.setName("Accounts");
    session.persist(singleDepartment);
    session.evict(singleDepartment);

    singleDepartment.setName("Accounts exception");
    singleDepartment.setId(1L);
    session.merge(singleDepartment);
    session.getTransaction().commit();

    Query querySaved = session.createQuery("Select e from SingleDepartment e where  id= 1",
        SingleDepartment.class);

    singleDepartment = (SingleDepartment) querySaved.getSingleResult();

    assertTrue("Accounts exception".equalsIgnoreCase(singleDepartment.getName()));

  }

Dentro de la clase session podemos ver dos aproximaciones más como Persist y Merge que no pertenecen a JPA por lo que es mejor evitarlos para hacer uso de los propios de JPA.

Excepción Detached Entity Passed to Persist con relación OneToMany

Vamos a continuar con el ejemplo de Department y vamos a asociarle un Employee con una relación 1 .. N.

@Getter
@Setter
@Entity
public class Department {

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

  @Column
  private String name;



}

Y ahora la entidad Employee:

@Getter
@Setter
@Entity
public class Employee {

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

  @Column
  private String name;

  @ManyToOne
  private Department department;
}

La relación la hemos establecido con el tipo de Cascada a Merge (CascadeType.Merge), con lo que solo propagaremos operaciones de tipo merge al department.

Para esta aproximación tenemos que tener en cuenta que si queremos persistir al empleado deberemos primero hacer un merge sobre el departamento.

  @Test
  public void given_department_when_detach_a_employee_with_merge_then_saved() {

    Employee employee = new Employee();
    employee.setName("Noel");
    Department departmentMerge = session.merge(this.department);
    employee.setDepartment(departmentMerge);

    session.persist(employee);
    session.getTransaction().commit();

    List<Employee> employees = session.createQuery("Select c from Employee c", Employee.class)
        .list();

    assertEquals(employees.size(), 1);
    assertTrue(employees.get(0).getName().equalsIgnoreCase("Noel"));

  }

Hay que tener en cuenta que el uso de persist en la relación si aplicamos Cascade ALL va a lanzar la excepción: org.hibernate.PersistentObjectException: detached entity passed to persist, ya que primero necesitamos hacer Merge sobre la entidad.

Si en la relación que tenemos entre department y employee realizamos un persist en lugar de merge obtendremos el error org.hibernate.PersistentObjectException` to JPA `PersistenceException` : detached entity passed to persist:

  @Test
  public void given_a_department_persist_when_new_employee_is_persist_then_exception_is_thrown() {

    department = new Department();
    department.setName("Accounts");
    session.persist(department);
    session.evict(department);
    department.setId(1L);

    Employee employee = new Employee();
    employee.setDepartment(department);

    session.persist(employee);
    assertThatThrownBy(() -> session.persist(department)).isInstanceOf(PersistenceException.class)
        .hasMessageContaining(
            "org.hibernate.PersistentObjectException` to JPA `PersistenceException` : detached entity passed to persist: com.refactorizando.example.detachentity.entity.Department");
    session.remove(employee);


  }

Conclusión

El Error en Hibernate Detached Entity Passed to Persist es un error muy típico que nos puede ocurrir en nuestras aplicaciones con Hibernate. El conocer los diferentes estados y el ciclo de vida de Hibernate nos ayudará a entender mejor los errores de Hibernate. En el caso de Detached Entity habrá que ver como estamos guardando la entidad o si hemos cerrado la sesión antes de guardarla, por lo que habrá que hacer uso de merge.

Si quieres ver un ejemplo en donde se produce este error puedes echar un vistazo a nuestro github.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *