防止删除级联到实体实例
Prevent delete from cascading to an entity instance
考虑以下 JPA 实体:
@Entity @Table(name = "product") class Product { ... }
@Entity @Table(name = "stock") class Stock {
@JoinColumn(name = "product_id", updatable = false)
@ManyToOne(fetch = FetchType.LAZY)
private Product product;
@Column(name = "quantity")
private Long quantity;
}
@Entity @Table(name = "cart") class Cart {
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "cart", orphanRemoval = true)
private List<CartItem> items = new ArrayList<>();
public void addItem(CartItem item) { items.add(item); }
public void removeItem(CartItem item) { items.remove(item); }
}
@Entity @Table(name = "cart_item") class CartItem {
@JoinColumn(name = "cart_id", updatable = false)
@ManyToOne(fetch = FetchType.LAZY)
private Cart cart;
@JoinColumn(name = "product_id", updatable = false)
@ManyToOne(fetch = FetchType.LAZY)
private Product product;
@Column(name = "quantity")
private Long quantity;
@JoinColumn(name = "stock_id", updatable = false)
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Stock stock;
public void setQuantity(Long quantity) {
final Long delta = this.quantity - Math.max(0L, quantity);
this.quantity += delta;
this.stock.setQuantity(this.stock.getQuantity() - delta);
if(quantity < 1) { cart.removeItem(this); }
}
}
注意从 CartItem
到 Stock
的关联。此关联已被注释,因此更改购物车商品数量会影响其在另一个方向的可用库存,即,如果购物车商品数量增加,则产品的可用库存数量会减少,反之亦然。
这让我可以触发 cartRepository.save(cart)
,保存所有购物车商品并同时更新它们的库存(由于 Cascade.ALL
从 Cart
到 CartItem
) .只要购物车商品的数量不为零,此方法就可以正常工作。
但是,当在调用 cart.removeItem(item)
之后调用 cartRepository.save(cart)
时,级联也会尝试删除购物车项目的库存,这不是本意。应删除购物车项目,但应仅更新其关联的库存。有没有一种方法可以将 CartItem
的更新级联到 Stock
,但在 CartItem
上级联删除作为 Stock
上的更新?
你的方法至少可以说是非常有问题的。如果多个线程修改同一个产品的数量会怎样? JPA 不防止并发访问!即使你不会 运行 技术上同步问题,你也会 逻辑上 因为(持久)数量将被错误的值覆盖(或者你得到一个OptimisticLockException
).
您应该避免这种头痛,将 数量 视为动态值,并使用 select 语句计算产品的当前数量。
如果您坚持缓存数量,您应该为此任务使用单独的服务,该服务使用适当的同步、并发映射等。
改变
class CartItem {
@JoinColumn(name = "stock_id", updatable = false)
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Stock stock;
}
至
class CartItem {
@JoinColumn(name = "stock_id", updatable = false)
@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY)
private Stock stock;
}
(CascadeType.ALL
到 { CascadeType.MERGE, CascadeType.PERSIST }
) 解决了这个问题。以前在调用 cartItem.setQuantity(0)
时会执行以下 SQL 查询:
delete from cart_item where id=?
delete from stock where id=?
更改后,根据需要执行以下 SQL 查询:
update stock set quantity=? where id=?
delete from cart_item where id=?
示例应用程序 available on Github 用于检查解决方案的正确性。
考虑以下 JPA 实体:
@Entity @Table(name = "product") class Product { ... }
@Entity @Table(name = "stock") class Stock {
@JoinColumn(name = "product_id", updatable = false)
@ManyToOne(fetch = FetchType.LAZY)
private Product product;
@Column(name = "quantity")
private Long quantity;
}
@Entity @Table(name = "cart") class Cart {
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "cart", orphanRemoval = true)
private List<CartItem> items = new ArrayList<>();
public void addItem(CartItem item) { items.add(item); }
public void removeItem(CartItem item) { items.remove(item); }
}
@Entity @Table(name = "cart_item") class CartItem {
@JoinColumn(name = "cart_id", updatable = false)
@ManyToOne(fetch = FetchType.LAZY)
private Cart cart;
@JoinColumn(name = "product_id", updatable = false)
@ManyToOne(fetch = FetchType.LAZY)
private Product product;
@Column(name = "quantity")
private Long quantity;
@JoinColumn(name = "stock_id", updatable = false)
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Stock stock;
public void setQuantity(Long quantity) {
final Long delta = this.quantity - Math.max(0L, quantity);
this.quantity += delta;
this.stock.setQuantity(this.stock.getQuantity() - delta);
if(quantity < 1) { cart.removeItem(this); }
}
}
注意从 CartItem
到 Stock
的关联。此关联已被注释,因此更改购物车商品数量会影响其在另一个方向的可用库存,即,如果购物车商品数量增加,则产品的可用库存数量会减少,反之亦然。
这让我可以触发 cartRepository.save(cart)
,保存所有购物车商品并同时更新它们的库存(由于 Cascade.ALL
从 Cart
到 CartItem
) .只要购物车商品的数量不为零,此方法就可以正常工作。
但是,当在调用 cart.removeItem(item)
之后调用 cartRepository.save(cart)
时,级联也会尝试删除购物车项目的库存,这不是本意。应删除购物车项目,但应仅更新其关联的库存。有没有一种方法可以将 CartItem
的更新级联到 Stock
,但在 CartItem
上级联删除作为 Stock
上的更新?
你的方法至少可以说是非常有问题的。如果多个线程修改同一个产品的数量会怎样? JPA 不防止并发访问!即使你不会 运行 技术上同步问题,你也会 逻辑上 因为(持久)数量将被错误的值覆盖(或者你得到一个OptimisticLockException
).
您应该避免这种头痛,将 数量 视为动态值,并使用 select 语句计算产品的当前数量。
如果您坚持缓存数量,您应该为此任务使用单独的服务,该服务使用适当的同步、并发映射等。
改变
class CartItem {
@JoinColumn(name = "stock_id", updatable = false)
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Stock stock;
}
至
class CartItem {
@JoinColumn(name = "stock_id", updatable = false)
@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY)
private Stock stock;
}
(CascadeType.ALL
到 { CascadeType.MERGE, CascadeType.PERSIST }
) 解决了这个问题。以前在调用 cartItem.setQuantity(0)
时会执行以下 SQL 查询:
delete from cart_item where id=?
delete from stock where id=?
更改后,根据需要执行以下 SQL 查询:
update stock set quantity=? where id=?
delete from cart_item where id=?
示例应用程序 available on Github 用于检查解决方案的正确性。