JPA 1:N关系中移除子项并不从父项中移除

huangapple go评论129阅读模式
英文:

JPA 1:N relationship removing child does not remove it from parent

问题

我有以下对象:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Entity(name="Group")
public class Group {
    @Id
    @GeneratedValue
    @NotNull
    @Column(name = "GROUP_ID")
    private Long id;

    @Column(name="NAME")
    private String name;

    @OneToMany(
            targetEntity = Product.class,
            mappedBy = "groupId",
            cascade = CascadeType.ALL,
            fetch = FetchType.EAGER,
            orphanRemoval = true
    )
    private List<Product> products = new ArrayList<>();

    public Group(String name) {
        this.name = name;
    }
}

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity(name="Product")
public class Product {
    @Id
    @GeneratedValue
    @NotNull
    @Column(name="PRODUCT_ID")
    private Long id;

    @Column(name="NAME")
    private String name;

    @Column(name="DESCRIPTION")
    private String description;

    @Column(name="PRICE")
    private double price;

    @ManyToMany
    @JoinTable(
            name = "JOIN_PRODUCT_CART",
            joinColumns = {@JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID")},
            inverseJoinColumns = {@JoinColumn(name = "CART_ID", referencedColumnName = "CART_ID")}
    )
    private List<CartEntity> carts = new ArrayList<>();

    @ManyToOne
    @JoinColumn(name = "GROUP_ID")
    private Group groupId;

    public Product(String name, String description, double price) {
        this.name = name;
        this.description = description;
        this.price = price;
    }

    public Product(String name, String description, double price, Group groupId) {
        this(name, description, price);
        this.groupId = groupId;
    }

    public void addToCart(CartEntity cart) {
        this.carts.add(cart);
        cart.getProductsList().add(this);
    }

    public void addGroup(Group group) {
        group.getProducts().add(this);
        this.groupId = group;
    }
}

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "cart")
public class CartEntity {

    @Id
    @NotNull
    @GeneratedValue
    @Column(name = "CART_ID")
    private Long id;

    @ManyToMany(cascade = CascadeType.ALL, mappedBy = "carts")
    private List<Product> productsList = new ArrayList<>();

    public void addProduct(Product product) {
        productsList.add(product);
        product.getCarts().add(this);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CartEntity that = (CartEntity) o;
        return id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

现在,当我有以下测试:

public class ProductDaoTestSuite {
    @Autowired
    private ProductDao productDao;
    @Autowired
    private CartDaoStub cartDaoStub;
    @Autowired
    private GroupDao groupDao;

    @Test
    public void testDeleteProduct() {
        // Given
        Product product = new Product("test", "testProduct", 100.0);
        Group group = new Group("group1");
        CartEntity cart = new CartEntity();

        product.addGroup(group);
        cart.addProduct(product);

        // When
        groupDao.save(group);
        productDao.save(product);
        cartDaoStub.save(cart);

        Long groupId = group.getId();
        Long productId = product.getId();
        Long cartId = cart.getId();

        productDao.deleteById(productId);

        // Then
        Assert.assertTrue(cartDaoStub.findById(cartId).isPresent());
        Assert.assertEquals(0, cartDaoStub.findById(cartId).get().getProductsList().size());

        Assert.assertTrue(groupDao.findById(groupId).isPresent());
        Assert.assertEquals(0, groupDao.findById(groupId).get().getProducts().size());

在删除产品后,我预期与其关联的组和购物车中的关联关系会消失(产品会从它们的List关联字段中消失)。然而,目前并未发生。当我在产品从数据库中检索时,从数据库中检索出的产品返回为null,但我使用Group/Cart Dao从数据库中检索组和购物车时,它们仍然在列表中具有产品。

我已尝试为@OneToMany注解添加"orphanRemoval = true"值,但对于Group实体似乎没有起作用。

我做错了什么?

我已经开始尝试为Product类上的@ManyToOne添加所有类型的级联(除了REMOVE),但到目前为止还没有成功。

英文:

I have the following objects:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Entity(name=&quot;Group&quot;)
public class Group {
@Id
@GeneratedValue
@NotNull
@Column(name = &quot;GROUP_ID&quot;)
private Long id;
@Column(name=&quot;NAME&quot;)
private String name;
@OneToMany(
targetEntity = Product.class,
mappedBy = &quot;groupId&quot;,
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
private List&lt;Product&gt; products = new ArrayList&lt;&gt;();
public Group(String name) {
this.name = name;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity(name=&quot;Product&quot;)
public class Product {
@Id
@GeneratedValue
@NotNull
@Column(name=&quot;PRODUCT_ID&quot;)
private Long id;
@Column(name=&quot;NAME&quot;)
private String name;
@Column(name=&quot;DESCRIPTION&quot;)
private String description;
@Column(name=&quot;PRICE&quot;)
private double price;
@ManyToMany
@JoinTable(
name = &quot;JOIN_PRODUCT_CART&quot;,
joinColumns = {@JoinColumn(name = &quot;PRODUCT_ID&quot;, referencedColumnName = &quot;PRODUCT_ID&quot;)},
inverseJoinColumns = {@JoinColumn(name = &quot;CART_ID&quot;, referencedColumnName = &quot;CART_ID&quot;)}
)
private List&lt;CartEntity&gt; carts = new ArrayList&lt;&gt;();
@ManyToOne
@JoinColumn(name = &quot;GROUP_ID&quot;)
private Group groupId;
public Product(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
public Product(String name, String description, double price, Group groupId) {
this(name, description, price);
this.groupId = groupId;
}
public void addToCart(CartEntity cart) {
this.carts.add(cart);
cart.getProductsList().add(this);
}
public void addGroup(Group group) {
group.getProducts().add(this);
this.groupId = group;
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = &quot;cart&quot;)
public class CartEntity {
@Id
@NotNull
@GeneratedValue
@Column(name = &quot;CART_ID&quot;)
private Long id;
@ManyToMany(cascade = CascadeType.ALL, mappedBy = &quot;carts&quot;)
private List&lt;Product&gt; productsList = new ArrayList&lt;&gt;();
public void addProduct(Product product) {
productsList.add(product);
product.getCarts().add(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CartEntity that = (CartEntity) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

Now, when I have the following test:

public class ProductDaoTestSuite {
@Autowired
private ProductDao productDao;
@Autowired
private CartDaoStub cartDaoStub;
@Autowired
private GroupDao groupDao;
@Test
public void testDeleteProduct() {
// Given
Product product = new Product(&quot;test&quot;, &quot;testProduct&quot;, 100.0);
Group group = new Group(&quot;group1&quot;);
CartEntity cart = new CartEntity();
product.addGroup(group);
cart.addProduct(product);
// When
groupDao.save(group);
productDao.save(product);
cartDaoStub.save(cart);
Long groupId = group.getId();
Long productId = product.getId();
Long cartId = cart.getId();
productDao.deleteById(productId);
// Then
Assert.assertTrue(cartDaoStub.findById(cartId).isPresent());
Assert.assertEquals(0, cartDaoStub.findById(cartId).get().getProductsList().size());
Assert.assertTrue(groupDao.findById(groupId).isPresent());
Assert.assertEquals(0, groupDao.findById(groupId).get().getProducts().size());

Following product deletion, I would expect association with it in group and cart to disappear (product to disappear from their List relationship fields). However, that is not happening at the moment. When I use Group/Cart Dao to pull group & cart from the DB after product deletion, they still have product in their Lists, while product when pulled from the DB is returned as null.
I have tried to add "orphanRemoval = true" value for @OneToMany adnotation, but it did not seem to work for Group entity.

What am I doing wrong?

I have started experimenting with adding all types of cascade (except for REMOVE) to @ManyToOne on Product class, but so far no luck.

答案1

得分: 2

For 1:N, yours should work just fine with minor adjustment.

The reason why it fails: Upon doing "groupDao.save(group);" this group is now in the persistence context and calling "groupDao.findById(groupId).get().getProducts().size();" would return the copy which is from the persistence context.

To solve this: simply add: entityManager.flush(); and entityManager.clear(); before the Assert

I would like to demonstrate it with this Integration Test

@Test
@Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
    ProductGroup group = groupRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
    Assert.assertEquals(1, group.getProducts().size());

    Product product = productRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
    productRepository.delete(product);

    entityManager.flush();
    entityManager.clear();

    Assert.assertEquals(0, productRepository.findAll().size());
    Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
}

If we are to remove the first 2 lines, then we won't need to flush and clear. Like this.

@Test
@Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
    Product product = productRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
    productRepository.delete(product);

    Assert.assertEquals(0, productRepository.findAll().size());
    Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
}

For N:M, since there would be another table where the product is being referenced, then we would need to delete the records from that table first before deleting the product.

N:M is a bit tricky so if I can suggest domain changes, here's how I'll do it. (The integration test is at the bottom.)

I'll add a separate entity: CartItem
which is associated with a Product and Cart

@Entity
public class CartItem {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    @ManyToOne
    private Product product;

    @ManyToOne
    private Cart cart;

    public String getId() {
        return id;
    }

    // Required by JPA
    protected CartItem() {}

}

And for the Product Entity: add a bidirectional relationship with CartItem

@Entity
public class Product {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    private String name;

    private String description;

    private BigDecimal price;

    @ManyToOne
    private ProductGroup group;

    @OneToMany(mappedBy = "product")
    private List<CartItem> cartItems;

    public List<CartItem> getCartItems() {
        return cartItems;
    }

    // Required by JPA
    protected Product() {}
}

Then, retrieve the product (using Join Fetch to avoid N+1, since later will be looping through each cartItem)

public interface ProductRepository extends JpaRepository<Product, String> {

    @Query("SELECT product FROM Product product JOIN FETCH product.cartItems")
    Optional<Product> findProduct(String Id);

}

create another query inside CartItemRepository to delete cartItems in bulk by ids

public interface CartItemRepository extends JpaRepository<CartItem, String> {

    @Modifying
    @Query("DELETE FROM CartItem cartItem WHERE cartItem.id IN :ids")
    void deleteByIds(@Param("ids") List<String> ids);

}

Lastly, here's the integration test to wrap everything up:

@Test
@Transactional
public void deleteProduct_associatedWithCart() {
    Cart cart = cartRepository.findById("0001").get();
    Assert.assertEquals(1, cart.getCartItems().size());

    Product product = productRepository.findProduct("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
    List<String> cartItemIds = product.getCartItems().stream()
            .map(CartItem::getId)
            .collect(Collectors.toList());

    cartItemRepository.deleteByIds(cartItemIds);
    productRepository.delete(product);

    entityManager.flush();
    entityManager.clear();

    Assert.assertEquals(0, productRepository.findAll().size());
    Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());

    Assert.assertEquals(0, cartItemRepository.findAll().size());
    Assert.assertEquals(0, cartRepository.findById("0001").get().getCartItems().size());
}

I've used DBUnit for this integration test, so I think it would also be helpful to share the dataset.

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <product_group id="0001" name="product group with 1 product"/>
    <product id="0001" group_id="0001" />

    <cart id="0001" />
    <cart_item id="0001" product_id="0001" cart_id="0001" />
</dataset>
英文:

For 1:N, yours should work just fine with minor adjustment.

The reason why it fails: Upon doing "groupDao.save(group);" this group is now in the persistence context and calling "groupDao.findById(groupId).get().getProducts().size()" would return the copy which is from the persistence context.

To solve this: simply add: entityManager.flush(); and entityManager.clear(); before the Assert

I would like to demonstrate it with this Integration Test

    @Test
@Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
ProductGroup group = groupRepository.findById(&quot;0001&quot;).orElseThrow(() -&gt; new IllegalArgumentException(&quot;id not found&quot;));
Assert.assertEquals(1, group.getProducts().size());
Product product = productRepository.findById(&quot;0001&quot;).orElseThrow(() -&gt; new IllegalArgumentException(&quot;id not found&quot;));
productRepository.delete(product);
entityManager.flush();
entityManager.clear();
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById(&quot;0001&quot;).get().getProducts().size());
}

If we are to remove the first 2 lines, then we won't need to flush and clear. Like this.

    @Test
@Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
Product product = productRepository.findById(&quot;0001&quot;).orElseThrow(() -&gt; new IllegalArgumentException(&quot;id not found&quot;));
productRepository.delete(product);
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById(&quot;0001&quot;).get().getProducts().size());
}

For N:M, since there would be another table where product is being referenced, then we would need to delete the records from that table first before deleting the product.

N:M is a bit tricky so if I can suggest domain changes, here how I'll do it. (The integration test is at the bottom.)

I'll add a separate entity: CartItem
which is associated to a Product and Cart

@Entity
public class CartItem {
@Id
@GeneratedValue(generator = &quot;uuid&quot;)
@GenericGenerator(name = &quot;uuid&quot;, strategy = &quot;uuid2&quot;)
private String id;
@ManyToOne
private Product product;
@ManyToOne
private Cart cart;
public String getId() {
return id;
}
// Required by JPA
protected CartItem() {}
}

And for the Product Entity: add a bidirectional relationship with CartItem

@Entity
public class Product {
@Id
@GeneratedValue(generator = &quot;uuid&quot;)
@GenericGenerator(name = &quot;uuid&quot;, strategy = &quot;uuid2&quot;)
private String id;
private String name;
private String description;
private BigDecimal price;
@ManyToOne
private ProductGroup group;
@OneToMany(mappedBy = &quot;product&quot;)
private List&lt;CartItem&gt; cartItems;
public List&lt;CartItem&gt; getCartItems() {
return cartItems;
}
// Required by JPA
protected Product() {}
}

Then, retrieve the product (using Join Fetch to avoid N+1, since later will be looping through each cartItem)

public interface ProductRepository extends JpaRepository&lt;Product, String&gt; {
@Query(&quot;SELECT product FROM Product product JOIN FETCH product.cartItems&quot;)
Optional&lt;Product&gt; findProduct(String Id);
}

create another query inside CartItemRepository to delete cartItems in bulk by ids

public interface CartItemRepository extends JpaRepository&lt;CartItem, String&gt; {
@Modifying
@Query(&quot;DELETE FROM CartItem cartItem WHERE cartItem.id IN :ids&quot;)
void deleteByIds(@Param(&quot;ids&quot;) List&lt;String&gt; ids);
}

Lastly here's the integration test to wrap everthing up:

@Test
@Transactional
public void deleteProduct_associatedWithCart() {
Cart cart = cartRepository.findById(&quot;0001&quot;).get();
Assert.assertEquals(1, cart.getCartItems().size());
Product product = productRepository.findProduct(&quot;0001&quot;).orElseThrow(() -&gt; new IllegalArgumentException(&quot;id not found&quot;));
List&lt;String&gt; cartItemIds = product.getCartItems().stream()
.map(CartItem::getId)
.collect(Collectors.toList());
cartItemRepository.deleteByIds(cartItemIds);
productRepository.delete(product);
entityManager.flush();
entityManager.clear();
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById(&quot;0001&quot;).get().getProducts().size());
Assert.assertEquals(0, cartItemRepository.findAll().size());
Assert.assertEquals(0, cartRepository.findById(&quot;0001&quot;).get().getCartItems().size());
}

I've used DBUnit for this integration test so I think it would also be helpful to share the dataset.

    &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt;
&lt;dataset&gt;
&lt;product_group id=&quot;0001&quot; name=&quot;product group with 1 product&quot;/&gt;
&lt;product id=&quot;0001&quot; group_id=&quot;0001&quot; /&gt;
&lt;cart id=&quot;0001&quot; /&gt;
&lt;cart_item id=&quot;0001&quot; product_id=&quot;0001&quot; cart_id=&quot;0001&quot; /&gt;
&lt;/dataset&gt;

答案2

得分: 0

当您移除一个实体时,此状态转换应从父级传播到子级,而不是反过来。

在这种情况下,您需要将该功能移动到Group实体中,类似于以下内容:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Entity(name="Group")
public class Group {
    @Id
    @GeneratedValue
    @NotNull
    @Column(name = "GROUP_ID")
    private Long id;

    @Column(name="NAME")
    private String name;

    @OneToMany(
            targetEntity = Product.class,
            mappedBy = "groupId",
            cascade = CascadeType.ALL,
            fetch = FetchType.LAZY, // 始终优先选择惰性初始化的集合而不是急切初始化的集合
            orphanRemoval = true
    )
    private List<Product> products = new ArrayList<>();

    public Group(String name) {
        this.name = name;
    }

    public void addProduct(Product product){
      product.setGroupId(this);
      this.products.add(product);
    }

    public void removeProduct(Product product){
      product.setGroupId(null);
      this.products.remove(product);
    }

如果要移除一个Product,您只需要调用removeProduct方法并保存父实体:

Group group = new Group("group1");
Product product = new Product("test", "testProduct", 100.0);

group.addProduct(product);

groupDao.save(group);

另一方面,我们有ProductCartEntity之间的多对多关系。

首先,如果您将实体CartEntity配置为与您的示例中一样使用Cascade.ALL

@ManyToMany(cascade = CascadeType.ALL, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();

它可能会产生一个不希望的效果:如果删除CartEntity,它也将删除与实体关联的所有Product,即使其他CartEntity仍然与它们关联。Vlad Mihalcea在这篇文章中详细解释了这个问题。

为了避免这个问题,最好的选择是将关系定义如下:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();

这将给我们一个类似这样的CartEntity

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "cart")
public class CartEntity {

    @Id
    @NotNull
    @GeneratedValue
    @Column(name = "CART_ID")
    private Long id;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "carts")
    private List<Product> productsList = new ArrayList<>();

    public void addProduct(Product product) {
        productsList.add(product);
        product.getCarts().add(this);
    }

    public void removeProduct(Product product) {
        productsList.remove(product);
        product.getCarts().remove(this);
    }

    public void removeProducts() {
        for(Product product : new ArrayList<>(products)) {
            removeProduct(product);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CartEntity that = (CartEntity) o;
        return id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

请注意,包括了removeProductremoveProducts方法。

使用此代码,如果您需要删除一个CartEntity,只需执行以下操作:

cart.removeProducts();
cartDao.remove(cart);

如果您需要从CartEntity移除一个Product(仅会删除关系):

cart.removeProduct(product);
cartDao.save(cart);

如果您需要将Product移除传播到CartEntity,我认为最好的选择是创建一个业务方法来处理整个过程。考虑类似于以下内容的方法:

public void removeProduct(Product product){
  Group group = product.getGroupId();
  group.removeProduct(product);

  final List<CartEntity> carts = product.getCarts();

  if (carts != null) {
    for(CartEntity cart : new ArrayList<>(carts)) {
      cart.removeProduct(product);
      cartDao.save(cart);
    }
  }

  groupDao.save(group);
}
英文:

When you remove an entity, this state transition should be propagated from parent to child, not the other way around.

In this case, you need to move that functionally to the Group entity, something like this:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Entity(name=&quot;Group&quot;)
public class Group {
    @Id
    @GeneratedValue
    @NotNull
    @Column(name = &quot;GROUP_ID&quot;)
    private Long id;

    @Column(name=&quot;NAME&quot;)
    private String name;

    @OneToMany(
            targetEntity = Product.class,
            mappedBy = &quot;groupId&quot;,
            cascade = CascadeType.ALL,
            fetch = FetchType.LAZY, // Always prefer LAZY initialized Collections to EAGER ones
            orphanRemoval = true
    )
    private List&lt;Product&gt; products = new ArrayList&lt;&gt;();

    public Group(String name) {
        this.name = name;
    }

    public void addProduct(Product product){
      product.setGroupId(this);
      this.products.add(product);
    }

    public void removeProduct(Product product){
      product.setGroupId(null);
      this.products.remove(product);
    }

If you want to remove a Product, you only need to invoke the removeProduct method and save the parent entity:

Group group = new Group(&quot;group1&quot;);
Product product = new Product(&quot;test&quot;, &quot;testProduct&quot;, 100.0);

group.addProduct(product);

groupDao.save(group);

On the other hand, we have the many-to-many relation between Product and CartEntity.

First, if you configure the entity CartEntity with Cascade.ALL as in your example:

@ManyToMany(cascade = CascadeType.ALL, mappedBy = &quot;carts&quot;)
private List&lt;Product&gt; productsList = new ArrayList&lt;&gt;();

It will have a probably undesired effect: if you remove the CartEntity, it will remove all the Products associated with the entity as well, even if other CartEntitys are still associated to them. Vlad Mihalcea explain it in great detail in this article.

To avoid that problem, the best option will be just define the relationship as follows:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = &quot;carts&quot;)
private List&lt;Product&gt; productsList = new ArrayList&lt;&gt;();

This will give us a CartEntity like this:

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = &quot;cart&quot;)
public class CartEntity {

    @Id
    @NotNull
    @GeneratedValue
    @Column(name = &quot;CART_ID&quot;)
    private Long id;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = &quot;carts&quot;)
    private List&lt;Product&gt; productsList = new ArrayList&lt;&gt;();

    public void addProduct(Product product) {
        productsList.add(product);
        product.getCarts().add(this);
    }

    public void removeProduct(Product product) {
        productsList.remove(product);
        product.getCarts().remove(this);
    }

    public void removeProducts() {
        for(Product product : new ArrayList&lt;&gt;(products)) {
            removeProduct(product);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CartEntity that = (CartEntity) o;
        return id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

Please, note the inclusion of the removeProduct and removeProducts methods.

With this code, if you need to remove a CartEntity, just do the following:

cart.removeProducts();
cartDao.remove(cart);

And if you need to remove a Product from the CartEntity (will only remove the relation):

cart.removeProduct(product);
cartDao.save(cart);

If you need to propagate the Product remove to the CartEntity, I think that the best option will be create a business method that takes care of the whole process. Think in something like:

public void removeProduct(Product product){
  Group group = product.getGroupId();
  group.removeProduct(product);

  final List&lt;CartEntity&gt; carts = product.getCarts();

  if (carts != null) {
    for(CartEntity cart : new ArrayList&lt;&gt;(carts)) {
      cart.removeProduct(product);
      cartDao.save(cart);
    }
  }

  groupDao.save(group);
}

答案3

得分: 0

它会移除关联,你只需要做一些小的调整。

  1. 1:N关系。 当你移除Product时,为了移除它与Group的关联,你不需要做任何其他操作,因为产品本身保存了这个关联(在数据库列product.group_id中)。你只需要提交事务。下次从数据库加载组时,它肯定不会包含这个产品。

  2. N:M关系。 由于关联信息存储在单独的表中,并且你没有单独的实体与之对应,没有自动移除关联的方法(在N:M关系中不应使用CascadeType.ALL)。你想要做的是,在移除产品之前先移除关联。只需在Product中添加另一个辅助方法。

public void removeFromCarts() {
        carts.forEach(c -> c.getProducts().remove(this));
        carts.clear();
}

所以最终,为了移除一个产品以及所有与之关联的内容,你需要执行以下步骤:

        product.removeFromCarts();
        productDao.deleteById(productId); // 不确定为什么使用id来移除(而不是传递对象)

请注意,你需要提交事务并关闭会话。因此,你不能依赖测试。在真实应用中,当你按照我描述的做时,它会起作用。

**N:M关系很棘手。例如,为了避免意外的SQL操作,最好使用Set而不是List。另外,从长远考虑,我建议你将N:M关系拆分为两个N:1和1:M关系,并为链接表创建一个专用实体。

英文:

It will remove the association, you just need to do small adjustments.

  1. 1:N. When you remove Product, you don't have to do anything else in order to remove its association with Group, because the product itself holds the association (in DB column product.group_id). You just need to commit the transaction. And next time when you load a group from the DB it for sure will not contain this product.
  2. N:M. There is no way to automatically remove the association because it is stored in a separate table and you don't have a separate entity for it. (YOU SHOULD NOT USE CascadeType.ALL for N:M relations). What you want to do is remove the association before you remove the product. Just add another helper method to Product.
public void removeFromCarts() {
        carts.forEach(c -&gt; c.getProducts().remove(this));
        carts.clear();
}

So finally, in order to remove a product and all the associations with it. You will need to do the following:

        product.removeFromCarts();
        productDao.deleteById(productId); // not sure why you remove by id (not pass object)

*please note that you need to commit transaction and close the session. So you cannot rely on the test. In real app when you do what I described, it will work

**N:M is tricky. For instance, you should better use Set instead of List to avoid unexpected SQL under the hood. Also going down the road, I recommend you to consider splitting N:M into two N:1 and 1:M and have a dedicated Entity for a link table

答案4

得分: 0

不太确定我理解了。Hibernate不会自动为您维护反向关联。您可以使其对关联的拥有方的更改变得敏感,但只能做到这一点。

至于为什么您的测试失败,cartDaoStub.findById(cartId)可能会返回已加载到持久化上下下文中的CartEntity的同一副本。尝试在进行断言之前调用entityManager.flush(),然后调用entityManager.clear(),问题可能会消失。

英文:

Not sure I follow. Hibernate does not automatically maintain the inverse association for you. You can make it sensitive to changes on the owning side of the association, but that's as far as it goes.

As to why your test fails, cartDaoStub.findById(cartId) probably returns the same copy of the CartEntity that you already have loaded into the persistence context. Try calling entityManager.flush() followed by entityManager.clear() before making the assertion and the issue will probably go away.

huangapple
  • 本文由 发表于 2020年8月22日 22:16:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/63537149.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定