0

I'm triying to save a user class with company and areas selected. User has a many to many relation with company and many to many to areas. It's giving me the error : detached entity passed to persist:

I'm not sure what is the problem

USER:

@Entity
@Table(name = "NPRO_USUARIOS")
public class User implements Serializable {

    private static final long serialVersionUID = -1330075515340995797L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO, generator="user_seq_gen")
    @SequenceGenerator(name="user_seq_gen", sequenceName="TELCO_NPRO_USER_SEQ")
    @NotNull
    private int id_usuario;

    @NotNull
    private String nombre_usuario;

    @ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
    @JoinTable(name = "NPRO_USUARIOS_SOCIEDADES_AREAS", joinColumns = @JoinColumn(name = "id_usuario"), inverseJoinColumns = @JoinColumn(name = "id_sociedad"))
    private Set<Sociedad> listaSociedad;

    @Transient
    private String sociedades;

    // Si el area es nula, el usuario estara asignado a todas las areas
    @ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
    @JoinTable(name = "NPRO_USUARIOS_SOCIEDADES_AREAS", joinColumns = @JoinColumn(name = "id_usuario"), inverseJoinColumns = @JoinColumn(name = "id_area"))
    private Set<Area> listAreas;

    @Transient
    private String areas;

    @NotNull
    private String matricula_usuario;

    @NotNull
    private String email_usuario;

    @ManyToMany(cascade = { CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
    @JoinTable(name = "NPRO_PERFILES_USUARIOS", joinColumns = @JoinColumn(name = "id_usuario"), inverseJoinColumns = @JoinColumn(name = "id_rol"))
    private Set<Role> listaRoles;

    @ManyToMany(cascade = { CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
    @JoinTable(name = "NPRO_PERFILES_USUARIOS", joinColumns = @JoinColumn(name = "id_usuario"), inverseJoinColumns = @JoinColumn(name = "id_pantalla"))
    private Set<Pantalla> listaPantallas;

    private LocalDateTime fecha_ultimo_acceso;
    private String observaciones;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "usuario_modif")
    private User usuario_modif;
}

Compnay:

@Entity
@Table(name = "NPRO_MAESTRO_SOCIEDADES")
public class Sociedad implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @NotNull
    private int id_sociedad;
    @NotNull
    private String cod_sociedad;
    @NotNull
    private String cod_sociedad_gl;
    @NotNull
    private String nombre_sociedad;
    @NotNull
    private String cif_sociedad;
    private String observaciones;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "usuario_modif")
    private User usuario_modif;

    private String activo;

    @JsonIgnore
    @ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
    @JoinTable(name = "NPRO_USUARIOS_SOCIEDADES_AREAS", joinColumns = @JoinColumn(name = "id_sociedad"), inverseJoinColumns = @JoinColumn(name = "id_usuario"))
    private Set<User> listaUsuarios;
}

Area:

@Entity
@Table(name = "NPRO_MAESTRO_AREAS")
public class Area implements Serializable {

    private static final long serialVersionUID = -1330075515340995797L;
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO, generator="area_seq_gen")
    @SequenceGenerator(name="area_seq_gen", sequenceName="TELCO_NPRO_AREAS_SEQ")
    @NotNull
    private int id_area;

    @NotNull
    private String nombre_area;

    private LocalDateTime fecha_modif;
    private String observaciones;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "usuario_modif")
    private User usuario_modif;

    @NotNull
    private String activo;

    @ManyToOne
    @JoinColumn(name="id_sociedad")
    private Sociedad sociedad;

    @JsonIgnore
    @ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
    @JoinTable(name = "NPRO_USUARIOS_SOCIEDADES_AREAS", joinColumns = @JoinColumn(name = "id_area"), inverseJoinColumns = @JoinColumn(name = "id_usuario"))
    private Set<User> listaUsuarios;
    }

I'm using springboot jpa repository save method

@Override
public User save(User user) {

    return userRepository.save(user);
}

And this is the complete error : 2020-06-09 15:49:02.371 [nio-8080-exec-4] .m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.telefonica.npro.model.Area; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.telefonica.npro.model.Area

Thanks in advance

EDIT :

I'm reading about the eror in this page http://knowledgespleasure.blogspot.com/2015/06/understand-detached-entity-passed-to.html

And I guess my problem is the last one :

On the other hand, if requirement is never to add a new child if its not alredy in DB then CascadeType.PERSIST should be removed and cascade={CascadeType.MERGE,CascadeType.REFRESH} should be used

User is always related with the company and areas, and they already exist, they are not going to be new. But if I remove PERSIST, it's triying to insert in an id null in the commun table NPRO_USUARIOS_SOCIEDADES_AREAS

Any help ?

Antonio Diaz
  • 133
  • 1
  • 1
  • 14
  • 1
    Does this answer your question? [PersistentObjectException: detached entity passed to persist thrown by JPA and Hibernate](https://stackoverflow.com/questions/13370221/persistentobjectexception-detached-entity-passed-to-persist-thrown-by-jpa-and-h) – SternK Jun 09 '20 at 14:52
  • Hi, I can't solve my problem with this explanation, At least I dont see how. – Antonio Diaz Jun 09 '20 at 15:59

1 Answers1

0

I will explain your problem for the @ManyToMany bidirectional relationship between User and Area entities.

  1. A bidirectional @ManyToMany association should have an owning and a mappedBy side. The CascadeType should be present only on one side of this association.

  2. As explained in this article, you need to have both sides in sync as otherwise, you break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized. For this reason, the User entity defines the addArea and removeArea entity state synchronization methods.

So, you should correct your User - Area @ManyToMany mapping in this way:

@Entity
@Table(name = "NPRO_USUARIOS")
public class User implements Serializable {
   // ...

   @ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
   @JoinTable(name = "NPRO_USUARIOS_SOCIEDADES_AREAS", joinColumns = @JoinColumn(name = "id_usuario"), inverseJoinColumns = @JoinColumn(name = "id_area"))
   private Set<Area> listAreas;

   public User()
   {
      listAreas = new HashSet<>();
   }

   public void addArea(Area area) {
      listAreas.add(area);
      area.getUsers().add(this);
   }

   public void removeArea(Area area) {
      listAreas.remove(area);
      area.getUsers().remove(this);
   }
}

@Entity
@Table(name = "NPRO_MAESTRO_AREAS")
public class Area implements Serializable {
    // ... 

    @JsonIgnore
    @ManyToMany(mappedBy = "listAreas")
    private Set<User> listaUsuarios;
}

And then you can save a new user in this way:

   User user = new User();
   // ...
   Area area1 = new Area();
   // ...
   user.addArea(area1);

   Area area2 = new Area();
   // ...
   user.addArea(area2);

   userRepository.save(user);

The similar correction should be done for the User - Sociedad relationship.

SternK
  • 11,649
  • 22
  • 32
  • 46
  • Hi. I did the change but still hae the same error, the problem is AREAS and SOCIEDADES are not new values, they are from BD, so , they are detached. If I remove the Cascade.PERSIST , that is the cause of this error, then I'm getting null value on one of the key for the join table... – Antonio Diaz Jun 10 '20 at 09:06
  • If you get `AREAS` and `SOCIEDADES` from DB in the current hibernate session, they should not be in detached state. But if you get these objects from the request you should at first obtain appropriate objects from db (with the same ids) then synchronize thay state with incoming in request objects and then save them. – SternK Jun 10 '20 at 09:21
  • But I see that very useless.... I already have the info, so, I dont need to go again to the db in order to get the same info... They are from the request, yes. Are there any way to solve this without going to the db again? – Antonio Diaz Jun 10 '20 at 09:24
  • 1
    Maybe [this](https://xebia.com/blog/jpa-implementation-patterns-saving-detached-entities/) article will be helpful. But shortly speaking, you can not just save that you get from request. These objects can be removed/modified by somebody else and you should appropriate handle these scenarios. You should also use one of the possible [locking](https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#locking) approaches. – SternK Jun 10 '20 at 09:55