Detached entity passed to persist in Spring-Data
Adding @Transactional to the method brings the entire discussion into one PersistenceContext alongwith optimising queries(fewer hibernate sql queries)
@Override
@Transactional
public Product save(Product product, String brandName) {
Brand brand = brandService.findByBrand(brandName);
if (brand == null) {
brand = brandService.save(brandName);
}
return productRepository.save(product);
}
Short answer:
There is no problem with your cascade annotation. You should not rely on automatic cascade and implement this logic by hand and inside your service layer.
Long answer:
You have two scenarios:
- Scenario 1 - CascadeType.ALL + existing brand = detached entity passed to persist
- Scenario 2 - CascadeType.MERGE + new brand = save the transient instance before flushin
Scenario 1 happens because JPA is trying to persist BRAND after persist PRODUCT (CascadeType.ALL). Once BRAND already exists you got an error.
Scenario 2 happend because JPA is not trying to persist BRAND (CascadeType.MERGE) and BRAND was not persisted before.
It's hard to figure out a solution because there are so many abstraction layers. Spring data abstracts JPA that abstracts Hibernate that abstracts JDBC and so on.
A possible solution would be use EntityManager.merge instead of EntityManager.persist so that CascadeType.MERGE could work. I belive you can do that re-implementing Spring Data save method. There is some reference about that here : Spring Data: Override save method
Another solution would be the short answer.
Example:
@Override
public Product save(Product product, String brandName) {
Brand brand = brandService.findByBrand(brandName);
if (brand == null) {
brand = brandService.save(brandName);
}
return productRepository.save(product);
}