我有以下对象:
@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());
在删除产品之后,我希望在组和购物车中与之的关联消失(产品从其“列表”关系字段中消失)。但是,目前还没有发生。当我使用Group / Cart Dao删除产品后从数据库中提取组和购物车时,他们的清单中仍然有产品,而从DB中提取的产品则返回null。 我尝试为@OneToMany注释添加“ orphanRemoval = true”值,但它似乎不适用于Group实体。
我在做什么错了?
我已经开始尝试将所有类型的层叠(除REMOVE除外)添加到Product类的@ManyToOne上,但是到目前为止还算不上成功。
答案 0 :(得分:2)
对于1:N ,您只需稍作调整就可以正常工作。
失败的原因:执行“ groupDao.save(group);” 后,此 group 现在位于持久性上下文,并调用“ groupDao.findById(groupId).get()。getProducts()。size()” 会返回来自持久性上下文的副本。
要解决此问题:,只需添加:entityManager.flush();和entityManager.clear();断言之前
我想通过集成测试进行演示
@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());
}
如果要删除前两行,则无需冲洗并清除。这样。
@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());
}
对于N:M ,由于还会有另一个表引用了产品,因此我们需要先删除该表中的记录,然后再删除产品。
N:M有点棘手,所以如果我可以建议域更改,请按以下步骤进行操作。 (集成测试在底部。)
我将添加一个单独的实体: CartItem 与产品和购物车
相关联@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() {}
}
对于产品实体:与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() {}
}
然后,检索产品(使用Join Fetch避免N + 1,因为稍后将遍历每个cartItem)
public interface ProductRepository extends JpaRepository<Product, String> {
@Query("SELECT product FROM Product product JOIN FETCH product.cartItems")
Optional<Product> findProduct(String Id);
}
在CartItemRepository内创建另一个查询,以按ID批量删除cartItems
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);
}
最后,这是整合一切的集成测试:
@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());
}
我已经使用DBUnit进行了集成测试,因此我认为共享数据集也将有所帮助。
<?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>
答案 1 :(得分:0)
remove
实体时,此状态转换应从父级传播到子级,而不是相反。
在这种情况下,您需要在功能上将其移至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, // Always prefer LAZY initialized Collections to EAGER ones
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);
}
如果要remove
和Product
,只需调用removeProduct
方法和save
父实体:
Group group = new Group("group1");
Product product = new Product("test", "testProduct", 100.0);
group.addProduct(product);
groupDao.save(group);
另一方面,我们在Product
和CartEntity
之间有多对多关系。
首先,如果您将CartEntity
实体配置为Cascade.ALL
,如示例所示:
@ManyToMany(cascade = CascadeType.ALL, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
这可能会产生不良影响:如果删除CartEntity
,即使其他Product
仍然存在,它也会删除与该实体关联的所有CartEntity
与他们相关联。 Vlad Mihalcea在this article中对其进行了详细说明。
为避免该问题,最好的选择是将关系定义如下:
@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);
}
}
请注意,包含了removeProduct
和removeProducts
方法。
使用此代码,如果您需要删除CartEntity
,只需执行以下操作:
cart.removeProducts();
cartDao.remove(cart);
如果您需要从remove
Product
中CartEntity
(只会删除该关系)
cart.removeProduct(product);
cartDao.save(cart);
如果您需要将Product
remove
传播到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);
}
答案 2 :(得分:0)
它将删除关联,您只需要进行一些小的调整即可。
Product
时,您无需执行其他任何操作即可删除与{{1}的关联},因为产品本身拥有关联(在数据库列Group
中)。您只需需要提交交易。下次当您从数据库加载组时,肯定不会包含该产品。product.group_id
)。您要做的是在删除产品之前删除关联。只需向CascadeType.ALL
添加另一种帮助方法。Product
因此,最后,为了删除产品及其所有关联。您将需要执行以下操作:
public void removeFromCarts() {
carts.forEach(c -> c.getProducts().remove(this));
carts.clear();
}
* 请注意,您需要提交事务并关闭会话。因此,您不能依靠测试。在真实的应用程序中,按照我的描述进行操作即可
** N:M很棘手。例如,您最好使用 product.removeFromCarts();
productDao.deleteById(productId); // not sure why you remove by id (not pass object)
而不是Set
来避免潜在的意外SQL。另外,我建议您考虑将N:M分为两个N:1和1:M,并为链接表使用专用的实体
答案 3 :(得分:0)
不确定我是否遵循。 Hibernate不会自动为您维护反向关联。您可以make it sensitive to changes on the owning side of the association,但就此为止。
关于测试失败的原因,cartDaoStub.findById(cartId)
可能返回与您已经加载到持久性上下文中的CartEntity
相同的副本。进行断言之前,尝试先致电entityManager.flush()
,再致电entityManager.clear()
,问题可能会消失。