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);

}