【问题标题】:Spring Data JPA - How to persist nested object any existing or notSpring Data JPA - 如何保留任何现有的嵌套对象
【发布时间】:2021-03-20 01:07:57
【问题描述】:

使用 Springboot 2.4.0,我正在尝试开发一个简单的模块来管理实体 ProductOrder 之间的典型关系(ManyToMany 关系)。

按顺序,我有这个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();

问题是:是否有可能使用以前的代码来保存新订单以及与之关联的产品(保存现有和非现有产品)?

这是订单库:

public interface OrderRepository extends JpaRepository<Order, Integer>

这是我目前得到的:

  • 如果我将 CascadeType 更改为 CascadeType.MERGE,我只能将现有产品链接到新订单,但对于新产品,我会收到以下错误:

org.hibernate.TransientObjectException:对象引用了一个未保存的瞬态实例 - 在刷新之前保存瞬态实例:com.gallelloit.productcrud.model.Product;嵌套异常是 java.lang.IllegalStateException:org.hibernate.TransientObjectException:对象引用了一个未保存的瞬态实例 - 在刷新之前保存瞬态实例:com.gallelloit.productcrud.model.Product

  • 我为 CascadeType 设置的任何其他值(包括 CascadeType.ALL)都得到相反的结果:新产品已保存并链接到新订单,但对于现有产品,我收到此错误:

传递给持久化的分离实体: com.galelloit.productcrud.model.Product;嵌套异常是 org.hibernate.PersistentObjectException:分离的实体传递给 坚持:com.gallelloit.productcrud.model.Product

我知道我可以“手动”获取每个产品,并将它们保存在 order.save() 之前,但我想知道 Spring Data 是否为您做到了。

有什么想法吗?

【问题讨论】:

    标签: java spring-boot orm spring-data-jpa spring-data


    【解决方案1】:

    在这种情况下依赖CascadeType.MERGE 是不安全的,因为它还会更新关联的Products,这意味着您可能会无意中覆盖现有的Product 数据。

    您可以通过要求 JPA 为现有实体创建代理而不是实际从数据库中获取它们的关联状态来优化现有产品的获取。这可以使用JpaRepository.getOne() 来完成。你需要这样的东西:

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

    如果您决定使用CascadeType.PERSISTCascadeType.ALL,当然可以将productRepository.save(product) 替换为product。不过,CascadeType.ALL@ManyToMany 没有意义,因为它暗示了 CascadeType.REMOVE,这是你肯定不想要的。

    另外,我假设保存订单的逻辑发生在事务上下文中,对吧?

    【讨论】:

    • 感谢您的回答。该代码将放置在哪里?我猜是在 OrderService 中,对吧?这是一个很好的解决方案。只有两个疑惑。一个不太重要的:它让我在 OrderServiceImpl 类中注入 productRepository。这没什么大不了的……但到目前为止我还没有这样做,现在我必须这样做。正如我所说,没什么大不了的。另一个是:什么产品不存在而getOne方法返回null?我会在列表中插入一个空值吗?会有什么行为?我是否必须“手动”处理这种可能性?
    • 本题报错:“另外一个是:什么产品不存在,getOne方法返回null?”它应该说:“另一个是:如果任何产品不存在并且 getOne 方法返回 null 怎么办?”
    • getOne 永远不会返回 null。 JPA 在使用getOne 时不检查实体的存在,这就是重点——避免额外的查询。如果事实证明 id 没有指向 PRODUCT 表中的有效行,那么您将从数据库中获得完整性约束违规,但
    • 那么完美。我最终以与您解释的方式类似的方式在 order.setProducts 中执行了一些验证。最后一个问题:JPA 创建的代理响应代理设计模式?或者 JPA 创建什么样的代理?您能否参考一些文章让我对它们有所了解?非常感谢!!
    • 最好的参考可能是Hibernate docs。是的,它遵循代理模式,从某种意义上说,如果您尝试访问其数据,它将被加载并返回,因此代理对您的应用程序来说大部分是透明的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多