【问题标题】:OneToMany bidirectional relationship JoinColumn value is null in Spring Data JPAOneToMany双向关系JoinColumn值在Spring Data JPA中为空
【发布时间】:2019-04-17 21:42:04
【问题描述】:

我有两个实体 Cart 和 CartProduct 的 OneToMany 双向映射。每当我们插入带有购物车产品的 Cart 对象时,CartProduct 表应该填充cart_id。这是问题所在,当我插入购物车对象时,一切似乎都很好,除了 JoinColumn(card_id) 导致 CartProduct 表中的值为空。我这样做对吗?

Cart.Java

package com.springtesting.model.cart;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.springtesting.model.AbstractAuditingEntity;
import com.springtesting.model.user.UserProfile;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@EqualsAndHashCode(callSuper = true)
@Entity
@Data
@Table(name = "cart")
public class Cart  extends AbstractAuditingEntity
{
    private static final long serialVersionUID = 6294902210705780249L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "user_profile_id")
    @JsonIgnoreProperties(value = {"addresses"})
    private UserProfile userProfile;

    @ManyToOne
    @JoinColumn(name = "cart_status")
    private CartStatus cartStatus;

    @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    //@ElementCollection(targetClass = CartProduct.class)
    private List<CartProduct> cartProducts=new ArrayList<>();

}

CartProduct.Java

package com.springtesting.model.cart;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.springtesting.model.AbstractAuditingEntity;
import com.springtesting.model.product.Product;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;

@EqualsAndHashCode(callSuper = true)
@Entity
@Data
@Table(name = "cart_product")
public class CartProduct extends AbstractAuditingEntity
{
    private static final long serialVersionUID = 6498067041321289048L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "product_id")
    private Product product;

    @Column(name = "quantity")
    private Integer quantity;


    @ManyToOne
    @JoinColumn(name = "cart_id",referencedColumnName = "id")
    @JsonIgnoreProperties(value = {"userProfile","cartStatus","cartProducts"})
    private Cart cart;

}

TestCase.java

 @Test
    public void insertCart()
    {
        Cart cart=new Cart();
        cart.setUserProfile(userProfileRepository.findAllByUserId(1L).get());
        cart.setCartStatus(cartStatusRepository.findById(1L).get());
        List<CartProduct> cartProducts=new ArrayList<>();

        CartProduct cartProduct=new CartProduct();
        cartProduct.setProduct(productRepository.findById(1L).get());
        cartProduct.setQuantity(2);
        cartProducts.add(cartProduct);

        cartProduct=new CartProduct();
        cartProduct.setProduct(productRepository.findById(2L).get());
        cartProduct.setQuantity(1);
        cartProducts.add(cartProduct);

        cart.setCartProducts(cartProducts);

        cartRepository.saveAndFlush(cart);

    }

【问题讨论】:

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


    【解决方案1】:

    是的,您的解决方法是添加 cartProduct.setCart(cart); 这是因为 CartProduct 是拥有实体并且是外键的保管者。上面的语句设置了 FK。

    思考这个问题的方式是owning entity的概念。当您拥有 mappedBy="cart" 时,您是在说 CartProduct 类拥有该关系。这意味着只有 CartProduct 类在进行持久化。这告诉 JPA 在 CartProduct 表中创建一个 FK。但是,我们注意到 CartProduct 上没有调用 save ,而是在 Cart 上调用了 save ,但 cartProducts 仍在保存。这是因为您有 cascade = CascadeType.ALL 注释。这告诉 JPA 在对 Cart 完成某些操作后级联这些操作,在本例中为 save 操作。

    您应该打印 SQL 语句并检查不同配置和测试用例的差异。这将帮助您更好地理解。

    您还有 FetchType.EAGER。这通常是一个坏习惯,通常会导致无穷无尽的问题。

    考虑双向映射的一个好方法是List&lt;CartProducts&gt; cartProducts 是一个仅限查询的字段。为了保存CartProduct,您可以直接在cartProductRepository 上调用保存。例如

    CartProduct cartProduct=new CartProduct();
    cartProduct.setProduct(productRepository.findById(1L).get());
    cartProduct.setQuantity(2);
    cartProduct.setCart(cart);
    cartProductRepository.save(cartProduct);
    

    然后

    cart.getCartProducts().add(cartProduct);
    

    并删除所有级联和急切的获取注释。当hibernate说你必须管理关系的双方时,这就是这个意思。

    这样做会导致一个保存查询。通过使用级联注释,您会发现当您将商品添加到购物车并对其调用 save 时,生成的 sql 将首先从数据库中删除所有现有的 cartProducts 商品,并在每次您使用新商品时重新添加它们调用保存。对于有 10 件物品而不是单个保存的购物车,您将有一个删除和 10 个新保存。肯定不太理想。如果您必须从头开始重新加载购物车,最有效的方法是获取购物车然后cart.setCartProducts(cartProductRepository.findAllByCart(cart));,这就是 FetchType.EAGER 正在做的事情。当您了解所有这些时,您就会意识到您的cartProducts. 不需要= new ArrayList&lt;&gt;();

    【讨论】:

      【解决方案2】:

      我想我找到了解决方案。基于Hibernate docs

      每当形成双向关联时,应用程序 开发者必须确保双方始终保持同步。

      所以我手动将cart 对象添加到cartProduct 对象中,从而将cart_id 保存在CartProduct 表中

      CartController.java

      import com.pj.springsecurity.model.cart.Cart;
      import com.pj.springsecurity.model.cart.CartProduct;
      import com.pj.springsecurity.repo.CartRepository;
      import org.springframework.web.bind.annotation.*;
      
      import java.util.List;
      import java.util.Optional;
      
      @RestController
      @RequestMapping("/api/v1/cart")
      public class CartController
      {
          private final CartRepository cartRepository;
      
          public CartController(CartRepository cartRepository)
          {
              this.cartRepository = cartRepository;
          }
      
          @GetMapping(path = "/list")
          public List<Cart> getAllCarts()
          {
              return cartRepository.findAll();
          }
      
          @GetMapping(path = "/find/user/{id}")
          public Optional<Cart> getCartBasedOnUserId(@PathVariable Long id)
          {
              return cartRepository.findAllByUserProfileUserId(id);
          }
      
          @PostMapping(path = "/product/add")
          public Cart addProductToCart(@RequestBody Cart cart)
          {
              List<CartProduct> cartProducts=cart.getCartProducts();
              for(CartProduct cartProduct: cartProducts)
              {
                  cartProduct.setCart(cart);
              }
              return cartRepository.saveAndFlush(cart);
          }
      
          @PutMapping(path = "/update")
          public Cart updateCart(@RequestBody Cart cart)
          {
              return cartRepository.saveAndFlush(cart);
          }
      
          @DeleteMapping(path = "/delete")
          public Cart createEmptyCart(@RequestBody Cart cart)
          {
              return cartRepository.saveAndFlush(cart);
          }
      
          @DeleteMapping(path = "/product/delete")
          public void deleteProductFromCart(@RequestBody Cart cart)
          {
              List<CartProduct> cartProducts=cart.getCartProducts();
              for(CartProduct cartProduct: cartProducts)
              {
                  cartProduct.setCart(null);
              }
              cartRepository.delete(cart);
          }
      }
      

      和测试用例更新相同

      @Test
      public void insertCart()
      {
          Cart cart=new Cart();
          cart.setUserProfile(userProfileRepository.findAllByUserId(1L).get());
          cart.setCartStatus(cartStatusRepository.findById(1L).get());
          List<CartProduct> cartProducts=new ArrayList<>();
      
          CartProduct cartProduct=new CartProduct();
          cartProduct.setProduct(productRepository.findById(1L).get());
          cartProduct.setQuantity(2);
          cartProduct.setCart(cart);
          cartProducts.add(cartProduct);
      
          cartProduct=new CartProduct();
          cartProduct.setProduct(productRepository.findById(2L).get());
          cartProduct.setQuantity(1);
          cartProduct.setCart(cart);
          cartProducts.add(cartProduct);
      
          cart.setCartProducts(cartProducts);
      
          cartRepository.saveAndFlush(cart);
      
      }
      

      【讨论】:

        猜你喜欢
        • 2017-05-29
        • 1970-01-01
        • 1970-01-01
        • 2016-01-09
        • 2021-02-10
        • 2019-10-25
        • 2021-11-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多