Spring Data JPA - 如何保留任何现有或不存在的嵌套对象
Spring Data JPA - How to persist nested object any existing or not
使用 Springboot 2.4.0,我正在尝试开发一个简单的模块来管理实体 Product
和 Order
之间的典型关系(多对多关系)。
按顺序,我有这个 products
字段:
@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;
因为我不需要它,所以现在我在 Product
实体中省略了一个 orders
字段。
在此关系之前,我有一个 Product
实体的 CRUD 并且正确 运行,因此我可以通过产品存储库插入产品:
public interface ProductRepository extends JpaRepository<Product, Integer>
我需要下一个包含多个产品的订单。我有这个代码:
List<Products> myListOfProducts = new ArrayList<>();
myListOfProducts.add(previouslyExistingProduct);
myListOfProducts.add(nonExistingProduct);
Order myorder = new Order(myListOfProducts, "mail", LocalDateTime.now());
myorder.save();
问题是:是否可以使用之前的代码来保存新订单以及 link 添加到其中的产品(同时保存现有和不存在的产品)?
这是订单库:
public interface OrderRepository extends JpaRepository<Order, Integer>
这是我目前得到的:
- 如果我将 CascadeType 更改为
CascadeType.MERGE
,我将 link 只有现有产品添加到新订单,但对于新产品,我会收到此错误:
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
- 我给 CascadeType 的任何其他值(包括
CascadeType.ALL
),我得到相反的结果:新产品被保存并 linked 到新订单,但对于现有产品,我得到这个错误:
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
我知道我可以“手动”获取每个产品,并在 order.save()
之前保存它们,但我想知道 Spring 数据是否为您完成了。
有什么想法吗?
在这种情况下依赖 CascadeType.MERGE
是不安全的,因为它也会更新关联的 Product
,这意味着您可能会无意中覆盖现有的 Product
数据。
您可以通过要求 JPA 为现有实体创建代理而不是实际从数据库中获取它们的关联状态来优化现有产品的获取。这可以使用 JpaRepository.getOne()
来完成。你需要这样的东西:
order.setProducts(order.getProducts()
.map(product -> product.getId() == null ? productRepository.save(product) : productRepository.getOne(product.getId())
.collect(Collectors.toList());
如果您决定使用 CascadeType.PERSIST
或 CascadeType.ALL
,您当然可以将 productRepository.save(product)
替换为 product
。 CascadeType.ALL
对 @ManyToMany
没有意义,因为它暗示 CascadeType.REMOVE
,你 肯定 不想要。
此外,我假设保存订单的逻辑发生在事务上下文中,对吧?
使用 Springboot 2.4.0,我正在尝试开发一个简单的模块来管理实体 Product
和 Order
之间的典型关系(多对多关系)。
按顺序,我有这个 products
字段:
@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;
因为我不需要它,所以现在我在 Product
实体中省略了一个 orders
字段。
在此关系之前,我有一个 Product
实体的 CRUD 并且正确 运行,因此我可以通过产品存储库插入产品:
public interface ProductRepository extends JpaRepository<Product, Integer>
我需要下一个包含多个产品的订单。我有这个代码:
List<Products> myListOfProducts = new ArrayList<>();
myListOfProducts.add(previouslyExistingProduct);
myListOfProducts.add(nonExistingProduct);
Order myorder = new Order(myListOfProducts, "mail", LocalDateTime.now());
myorder.save();
问题是:是否可以使用之前的代码来保存新订单以及 link 添加到其中的产品(同时保存现有和不存在的产品)?
这是订单库:
public interface OrderRepository extends JpaRepository<Order, Integer>
这是我目前得到的:
- 如果我将 CascadeType 更改为
CascadeType.MERGE
,我将 link 只有现有产品添加到新订单,但对于新产品,我会收到此错误:
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
- 我给 CascadeType 的任何其他值(包括
CascadeType.ALL
),我得到相反的结果:新产品被保存并 linked 到新订单,但对于现有产品,我得到这个错误:
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
我知道我可以“手动”获取每个产品,并在 order.save()
之前保存它们,但我想知道 Spring 数据是否为您完成了。
有什么想法吗?
在这种情况下依赖 CascadeType.MERGE
是不安全的,因为它也会更新关联的 Product
,这意味着您可能会无意中覆盖现有的 Product
数据。
您可以通过要求 JPA 为现有实体创建代理而不是实际从数据库中获取它们的关联状态来优化现有产品的获取。这可以使用 JpaRepository.getOne()
来完成。你需要这样的东西:
order.setProducts(order.getProducts()
.map(product -> product.getId() == null ? productRepository.save(product) : productRepository.getOne(product.getId())
.collect(Collectors.toList());
如果您决定使用 CascadeType.PERSIST
或 CascadeType.ALL
,您当然可以将 productRepository.save(product)
替换为 product
。 CascadeType.ALL
对 @ManyToMany
没有意义,因为它暗示 CascadeType.REMOVE
,你 肯定 不想要。
此外,我假设保存订单的逻辑发生在事务上下文中,对吧?