0

I am new using Spring Boot and Hibernate and I am working on a project that has a simple Many to Many association. A sport Center has many services and viceversa. When i try to enter a json like this to create a new sport center and associate some existing service:

{
"name" : "SC1",
"address" : "Street 1111",
"description" : "A Sport center",
"email" : "sc@sc.com",
"phones" : [{"number" : "123456"}, {"number" : "654321"}],
"services" : [{"idService" : 1}, {"idService" : 2}],
"commune" : {"idCommune" : 1},
"users" : [{"idUser":62}]
}

I am receiving this error:

2018-06-25 03:56:32.670 DEBUG 20696 --- [nio-8080-exec-6] org.hibernate.SQL                        : insert into sport_center (address, Commune_idCommune, description, email, name) values (?, ?, ?, ?, ?)
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Street 1111]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [INTEGER] - [1]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [A Sport center]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [sc@sc.com]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [5] as [VARCHAR] - [SC1]
2018-06-25 03:56:32.800 DEBUG 20696 --- [nio-8080-exec-6] org.hibernate.SQL                        : insert into phone (number, Sport_center_idSport_Center) values (?, ?)
2018-06-25 03:56:32.800 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [123456]
2018-06-25 03:56:32.800 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [INTEGER] - [252]
2018-06-25 03:56:32.942 DEBUG 20696 --- [nio-8080-exec-6] org.hibernate.SQL                        : insert into phone (number, Sport_center_idSport_Center) values (?, ?)
2018-06-25 03:56:32.942 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [654321]
2018-06-25 03:56:32.942 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [INTEGER] - [252]
2018-06-25 03:56:33.368 ERROR 20696 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.eiffel.canchai.model.Service; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.eiffel.canchai.model.Service] with root cause

org.hibernate.PersistentObjectException: detached entity passed to persist: com.eiffel.canchai.model.Service

It is important to mention that I have already added some rows in the service table. So, the idea is to add a Sport Center and associate these services to it.

Could you please help me with this?

My classes below:

SportCenter.java

@Entity
@Table(name = "sport_center")
public class SportCenter implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)    
@Column(name = "idSport_Center")
private Integer idSportCenter;

@Column(name = "name")
private String name;

@Column(name = "address")
private String address;

@Column(name = "description")
private String description;

@Column(name = "email")
private String email;

@JoinTable(name = "sport_center_has_user", joinColumns = {
    @JoinColumn(name = "Sport_center_idSport_Center", referencedColumnName = "idSport_Center")}, inverseJoinColumns = {
    @JoinColumn(name = "User_idUser", referencedColumnName = "idUser")})
@ManyToMany(cascade = CascadeType.ALL)
private List<User> users;


@JoinTable(name = "sport_center_has_service", joinColumns = {
@JoinColumn(name = "Sport_center_idSport_Center", referencedColumnName = "idSport_Center")}, inverseJoinColumns = {    
@JoinColumn(name = "Service_idService", referencedColumnName = "idService")})
@ManyToMany(cascade = CascadeType.ALL)    
private List<Service> services;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "sportCenter")
@JsonIgnore
private List<ImageField> imageFields;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "sportCenter")
@JsonIgnore
private List<Field> fields;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "sportCenter", fetch = FetchType.LAZY)
private List<Phone> phones;

@JoinColumn(name = "Commune_idCommune", referencedColumnName = "idCommune")
@ManyToOne(optional = false)    
private Commune commune;


public SportCenter() {
}

Service.java

@Entity
@Table(name = "service")
public class Service implements Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)    
@Column(name = "idService")
private Integer idService;

@Column(name = "name")
private String name;           

@ManyToMany(fetch = FetchType.LAZY, mappedBy = "services", cascade = CascadeType.ALL) 
@JsonIgnore
private List<SportCenter> sportCenters;

public Service() {
}

ServiceDao.java

@Repository
@Transactional
public class ServiceDao implements IServiceDao {

@PersistenceContext
private EntityManager entityManager;

@Override
public void save(Service entity) {
    entityManager.persist(entity);      
}

The SportCenterDao is almost the same that the above :)

Controller

@Controller
@RequestMapping("/sportcenter")
@CrossOrigin
public class SportCenterController {

@Autowired
private ISportCenterService  sportCenterService;

@PostMapping("/register")
public ResponseEntity<?> registerSportCenter(@RequestBody SportCenter sc){      
    if (sc.getEmail().length() == 0 || sc.getName().length() == 0 || sc.getAddress().length() == 0 || sc.getPhones().size() == 0 || sc.getServices().size() == 0) {
        return new ResponseEntity(new ErrorMsg("Check parameters. One of them is missing"),HttpStatus.NO_CONTENT);
    }

    for (Phone p : sc.getPhones()) {
        p.setSportCenter(sc);           
    }

    for (Service s : sc.getServices()) {
        s.addSportCenters(sc);              
    }

    sportCenterService.save(sc);
    return new ResponseEntity(HttpStatus.OK);
}

I think that probably is an error mapping the associations but I am not sure.

Also, if you see that there is a way to improve my code, please let me know :).. Advices are always good.

I am using Spring Boot V2.0.3 and Hibernate 5.3

Thanks in advance!

Ramon Paris
  • 127
  • 2
  • 11
  • Do the services with Id 1&2 actually exist? If not that is probably the reason. If they do, you probably need to reference them with an URL anyway. Because I'd expect the current API usage to set all attributes of them to `null` anyway. *Note: I'm just guessing, I hardly ever used Spring Data Rest* – Jens Schauder Jun 25 '18 at 08:44
  • @JensSchauder Yes, they exist. Sorry, I did not understand. What do you mean with reference them with an URL? – Ramon Paris Jun 25 '18 at 13:13
  • Something like this: https://stackoverflow.com/a/13031580/66686 – Jens Schauder Jun 25 '18 at 13:30
  • @JensSchauder thanks a lot for you answer! I didn't know that I could do that :).. I did not do exactly this but it helps me a lot to figure it out what was the problem. Thanks again!! – Ramon Paris Jun 26 '18 at 15:17

1 Answers1

0

The problem here is that you are cascading the persist event from parent to child.

When the child entity has a non-zero ID, then hibernate will assume it already exists. Since you are getting the data from the rest service, I guess the child "Service" entities are not detached. Hence the error "Detached entity passed to persist".

You need to persist the parent first, make the children managed via merge call, then link the parent to the child entities, or get rid of the Cascade.All.

Nullbeans
  • 309
  • 1
  • 8