Spring Data JPA中的OneToMany双向关系JoinColumn值为null

时间:2019-04-17 21:42:04

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

我对两个实体Cart和CartProduct有一个OneToMany双向映射。每当我们插入带有购物车产品的购物车对象时,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);

    }

2 个答案:

答案 0 :(得分:1)

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

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

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

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

考虑双向映射的一种好方法是List<CartProducts> cartProducts是仅查询字段。为了保存CartProduct,您可以直接在cartProductRepository上调用save。例如

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

然后

cart.getCartProducts().add(cartProduct);

并删除所有级联和渴望获取注释。当休眠状态告诉您必须管理关系的双方时,这是什么意思。

以这种方式执行此操作将导致查询一次保存。通过使用级联批注,您会发现在将商品添加到购物车并调用save时,生成的sql首先会从数据库中删除所有现有的cartProducts商品,然后将其与新商品一起重新添加您调用保存的时间。对于包含10个项目而不是一个保存的购物车,您将被删除并保存10个新的保存。绝对不太理想。如果必须从头开始重新加载购物车,最有效的方法是先获取购物车,然后再使用cart.setCartProducts(cartProductRepository.findAllByCart(cart));,这是FetchType.EAGER所做的。了解了所有这些内容后,您就会意识到= new ArrayList<>();

不需要cartProducts.

答案 1 :(得分:0)

我想我想出了解决方案。基于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);

}