Detached entity passed to persist in Spring-Data
hibernate, java, spring-data
Solution
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);
}
Problem
I have two tables in my db `Brand` and `Product` with the next simple structure: | Brand | id PK | | Product | id PK | brand_id FK | and entities for that tables: ``` @Entity @Table(name = "Brand") public class Brand { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "brand") private String brand; /* getters and setters */ } ``` ``` @Entity @Table(name = "Product") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "brand_id") private Brand brand; /* getters and setters */ } ``` As I use Spring-Data I have repository and service with implementation for Brand: ``` @Repository public interface BrandRepository extends JpaRepository<Brand, Long> { Brand findByBrand(String brand); } ``` ``` public interface BrandService { Brand findByBrand(String brand); } ``` ``` @Service public class BrandServiceImpl implements BrandService { @Autowired private BrandRepository brandRepository; @Override public Brand findByBrand(String brand) { return brandRepository.findByBrand(brand); } } ``` and for Product: ``` @Repository public interface ProductRepository extends JpaRepository<Product, Long> { } ``` ``` public interface ProductService { Product save(Product product); } ``` ``` @Service public class ProductServiceImpl implements ProductService { @Autowired private ProductRepository productRepository; @Override public Product save(Product product) { return productRepository.save(product); } } ``` The goal is to save Product object. Brand object should be saved automatically if it doesn't exist in db or should be set to Product otherwise: ``` Brand brand = brandService.findByBrand(brandName); if (brand == null) { brand = new Brand(); brand.setBrand("Some name"); } product.setBrand(brand); productService.save(product); ``` It works fine if Brand object with specified brandName is not in my db. But if it is I get: ``` PersistentObjectException: detached entity passed to persist ``` for Brand. I can change cascade type to MERGE and it will work fine. But if I run the code with MERGE cascade type and Brand object with specified brandName is not in my db I get ``` IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing ``` for Brand (that's really not surprised). What Cascade Type should be? Ot what I did wrong?