detached entity passed to persist JPA Spring boot save - java

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 ?

I will explain your problem for the #ManyToMany bidirectional relationship between User and Area entities.
A bidirectional #ManyToMany association should have an owning and a mappedBy side. The CascadeType should be present only on one side of this association.
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.

Related

java manytomany mapping not creating

I created two simple entities for trying out the java persistence manytomany mapping. But whatever I try, the jointable won't be populated with a mapping and remains empty.
UserClass:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(targetEntity = Order.class ,fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "users_orders",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "order_id", referencedColumnName = "id")
)
#JsonIgnoreProperties(value = "orderUsers")
private Set<Order> userOrders = new HashSet<>();
}
OrderClass:
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(mappedBy = "userOrders", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "userOrders")
private Set<User> orderUsers = new HashSet<>();
}
I added Getter/Setter/Constructor via Lombok.
Create and save an user. Create an order, add the user and save it. But still the jointable remains empty.
Any ideas?

Hibernate OneToMany mapping & Query generation : More than one row with the given identifier was found

I am using spring-boot-starter-data-jpa 1.5.1.RELEASE which internally uses hibernate-core 5.0.11.Final
My entity looks like this:
AreaDto
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH)
#JoinColumn(name = "AREA_ROWID")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FESTIVAL", joinColumns = {
#JoinColumn(name = "AREA_ROWID", referencedColumnName = "AREA_ROWID")}, inverseJoinColumns = {
#JoinColumn(name = "FESTIVAL_ROWID", referencedColumnName = "FESTIVAL_ROWID")})
private Collection<ActionDto> festivalActions;
}
FestivalDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
ActionDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}
I'm trying to make sense of the below ideas:
What is the strategy used by hibernate to decide on the festival_rowid (or festival_row ids) used to get all the associated action? How will hibernate generated SQL query vary if i change festivalActions fetch strategies between LAZY and EAGER? I know about proxying, collection proxying and all, my question is specific to how those sql is generated and how it may have an impact on deciding the value of bind parameter.
Is my mapping accurate or should I be using a multimap for this relationship since an area could have multiple festival and each festival could have multiple actions
Background:
I am getting below error which goes away if I change the fetch type from LAZY to EAGER. Hoping to understand the behaviour for gaining some confidence in the fix. I have read SO and error
org.hibernate.HibernateException: More than one row with the given identifier was found: data.dto.ActionDto#280856b5
This mapping does not make much sense. You can't map festivalActions this way because there is no way to persist the state properly through such a mapping. Also festival in AreaDto should be mapped by the area in FestivalDto. Try the following instead:
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
public Collection<ActionDto> getFestivalActions() {
return festival.stream().flatMap(f -> f.actions.stream()).collect(Collectors.toList());
}
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}

Why JpaRpository.delete(entity) don't actually delete the entity?

I have this entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Data
#Entity
#Table(name = "visits")
public class Visit {
#EqualsAndHashCode.Include
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull
#Column(nullable = false, updatable = false)
private long id;
#Column
private LocalDate date;
#Column
private LocalTime startTime;
#Column
private LocalTime endTime;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "client_id")
private Client client;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
private Employee employee;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "services_booked",
joinColumns = {#JoinColumn(name = "visit_id")},
inverseJoinColumns = {#JoinColumn(name = "service_id")}
)
private Set<Service> servicesBooked = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "visit")
private Set<TimeSlot> timeSlots = new HashSet<>();
}
My controller performs delete action on service bean into transaction:
#Transactional
#DeleteMapping("/{id}")
public ResponseEntity<DeleteVisitResponse> deleteVisit(#PathVariable("id") long visitId,
#RequestAttribute(USER_AUTH) UserAuthDto userAuthDto) {
// some logic
Optional<Visit> visit = visitService.findVisitById(visitId);
// check isPresent via Optional.map
visitService.deleteVisit(visit.get());
// constructing the response
}
And service bean just deletes it:
#Override
public void deleteVisit(#NonNull Visit visit) {
visitRepository.delete(visit);
}
But actually it does not delete it. It performs sequential selects to resolve chained entities. Here are log records of that deletion: https://gist.github.com/bvn13/906582ad39720e033c24ddd6f59f906c
That's all. Why it cannot perform deleting operation?

Unable to save data using hibernate.save()

I am trying to add Staff object which has staff information, roles and subjects. The code below saves only Staff data not its associated collections data.
I have tried to debug it but didn't understand the issue. The staff object has roles and subjects before saving into database but they are not getting saved in the DB. Surprisingly, the similar code is working while saving Course table data; however, Course does also have collection of Subject class.
It seems to me that Subjects have Course object which may be creating problem, I am not sure why. Please advise how to fix it.
The complete project is available on GitHub(https://github.com/ravinain/practice/tree/master/Java/Spring/SchoolProject)
Staff.java
#Entity
#Table
public class Staff extends Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private double salary;
#OneToMany(mappedBy = "staff", fetch=FetchType.EAGER)
#Cascade({ CascadeType.ALL})
private Set<Role> roles = new HashSet<Role>();
#ManyToMany(mappedBy = "staffs", fetch=FetchType.EAGER)
#Cascade({CascadeType.ALL})
private Set<Subject> subjects = new HashSet<Subject>();
Role.java
#Entity
#Table
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#NotNull
private int id;
private String name;
#ManyToOne
#JoinTable(name = "role_staff", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "staff_id", referencedColumnName = "id"))
#JsonIgnore
private Staff staff;
Subject.java
#Entity
#Table
public class Subject implements Comparable<Subject>{
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "subject_staff", joinColumns = #JoinColumn(name = "subject_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "staff_id", referencedColumnName = "id"))
#JsonIgnore
private Set<Staff> staffs = new HashSet<Staff>();
#ManyToMany(mappedBy = "subjects", fetch = FetchType.EAGER)
#Cascade({ CascadeType.DELETE, CascadeType.SAVE_UPDATE })
#JsonIgnore
private Set<Course> courses = new HashSet<Course>();
#ManyToMany(mappedBy = "subjects", fetch = FetchType.EAGER)
#Cascade({ CascadeType.DELETE, CascadeType.SAVE_UPDATE })
#JsonIgnore
private Set<Student> students = new HashSet<Student>();
DAO Code:
public Staff addStaff(Staff staff) {
Session session = sessionFactory.getCurrentSession();
session.save(staff);
return staff;
}
POST Request:
{"name":"New Test","age":22,"gender":"Female","salary":12000,"roles":[{"id":3,"name":"Teacher"}],"subjects":[{"id":1,"description":"Math"},{"id":2,"description":"English"}]}

Spring JPA: When I delete an entity, the entities related are deleted too

First of all, thanks for be interested in this question.
The scenario is like that: there is an entity Usuario (user) which has several Role. When I delete an User, all Roles related are deleted too.
The code for Role is:
#Entity
#Table(name = "marte_role")
#XmlRootElement
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String nombre;
#ManyToMany(
fetch = FetchType.EAGER,
targetEntity = Usuario.class,
cascade = { CascadeType.ALL })
#JoinTable(
name = "marte_usuario_role",
joinColumns = { #JoinColumn(name = "role_id") },
inverseJoinColumns = { #JoinColumn(name = "usuario_id") })
#JsonIgnore
private List<Usuario> users = new ArrayList<Usuario>();
... Getters/setters/builders...
And the code for Usuario is:
#Entity
#Table(name = "marte_usuario")
#XmlRootElement
public class Usuario implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String password;
private String email;
private boolean enabled;
#ManyToMany(
fetch = FetchType.EAGER
, targetEntity = Role.class
, cascade = { CascadeType.ALL })
#JoinTable(
name = "marte_usuario_role"
, joinColumns = { #JoinColumn(name = "usuario_id") }
, inverseJoinColumns = { #JoinColumn(name = "role_id") })
private List<Role> roles = new ArrayList<Role>();
#Transient
private int numRoles;
It seems to me that is related with CascadeType.ALL. I've tested with CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE, instead of CascadeType.ALL and then the entity is NOT deleted.
Does anyone know what I am doing wrong?
Thanks in advance for your answers.
CascadeType.ALL include also CascadeType.REMOVE, that's why your entities are removed with this annotation.
You're not doing anything wrong. You specify CascadeType.ALL, which means all operations, including delete, are cascaded to related entities. If you don't want that to happen, don't use CascadeType.ALL.
Solved!
The answers provided are both correct: remove CascadeType.ALL, but just in the Role entity. With this change is possible to remove an Usuario, without deleting all the Role related.
#Entity
#Table(name = "marte_role")
#XmlRootElement
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String nombre;
#ManyToMany(
fetch = FetchType.EAGER,
targetEntity = Usuario.class
)
#JoinTable(
name = "marte_usuario_role",
joinColumns = { #JoinColumn(name = "role_id") },
inverseJoinColumns = { #JoinColumn(name = "usuario_id") })
#JsonIgnore
private List<Usuario> users = new ArrayList<Usuario>();
...
Thanks!

Categories

Resources