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?
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 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);
}
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);
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.