Spring Data JPA - How to persist nested object any existing or not

Viewed 2743

Using Springboot 2.4.0, I am trying to develop a simple module to manage a tipical relationship between the entities Product and Order (ManyToMany relationship).

In Order, I have this products field:

@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(
        name="products_orders",
        joinColumns=@JoinColumn(name="order_id"),
        inverseJoinColumns=@JoinColumn(name="product_id")
)
private List<Product> products;

Since I don't need it, for now I am omitting an orders field in the Product entity.

Previous to this relationship, I have a CRUD for Product entity up and properly running, so I can insert products through a Product Repository:

public interface ProductRepository extends JpaRepository<Product, Integer>

I need to place an order with a number of products within it. I have this code:

List<Products> myListOfProducts = new ArrayList<>();

myListOfProducts.add(previouslyExistingProduct);
myListOfProducts.add(nonExistingProduct);

Order myorder = new Order(myListOfProducts, "mail", LocalDateTime.now());
myorder.save();

The question is: is it possible to get with the previous code that the new order gets saved as well as the products linked to it (saving both the existing and non existing products)?

This is the Order Repository:

public interface OrderRepository extends JpaRepository<Order, Integer>

This is what I got so far:

  • If I change CascadeType to CascadeType.MERGE, I get to link only existing products to the new order, but for new Products, I get this error:

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.gallelloit.productcrud.model.Product; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.gallelloit.productcrud.model.Product

  • Any other value I put to CascadeType (including CascadeType.ALL), I get the opposite: new products are saved and linked to the new order, but for existing products, I get this error:

detached entity passed to persist: com.gallelloit.productcrud.model.Product; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.gallelloit.productcrud.model.Product

I know that I can do it "manually" getting each of the products, and saving them before the order.save(), but I was wondering if Spring Data did it for you.

Any idea?

1 Answers

Relying on CascadeType.MERGE is not safe in this context, because it updates the associated Products as well, meaning you may unintentionally overwrite existing Product data.

You can optimize the fetching of existing products by asking JPA to create proxies for the existing entities instead of actually fetching their associated state from the DB. This can be done using JpaRepository.getOne(). You'll need sth like:

order.setProducts(order.getProducts()
    .map(product -> product.getId() == null ? productRepository.save(product) : productRepository.getOne(product.getId())
    .collect(Collectors.toList());

If you decide on using CascadeType.PERSIST or CascadeType.ALL, you can of course replace productRepository.save(product) with just product. CascadeType.ALL doesn't make sense for @ManyToMany, though, since it implies CascadeType.REMOVE, which you most certainly don't want.

Also, I'm assuming the logic of saving the order happens inside a transactional context, right?

Related