我需要允许用户编辑订单,包括添加新的订单项目和这些订单项目下的子项目。我相应地对三个实体进行了建模,Order
、OrderItem
s和OrderSubItem
s。每个OrderItem
必须有一个或多个OrderSubItem
(为简洁起见,省略其他实体道具):
@Entity
@Table(name="[Order]")
@Data
public class Order {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
@JoinColumn(name="orderId", nullable=false)
@Valid
@NotNull(message="order.orderItems.notNullorEmpty")
@Size(min=1, message="order.orderItems.notNullorEmpty")
private List<OrderItem> orderItems;
}
@Entity
@Data
public class OrderItem {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
@JoinColumn(name="orderId", nullable=false)
@Valid
@NotNull(message="order.orderSubItems.notNullorEmpty")
@Size(min=1, message="order.orderSubItems.notNullorEmpty")
private List<OrderSubItem> ordersSubItems;
@ToString.Exclude
@JsonIgnore
@ManyToOne
@JoinColumn(name="orderId", insertable=false, updatable=false)
private Order order;
}
@Entity
@Data
public class OrderSubItem {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@ToString.Exclude
@JsonIgnore
@ManyToOne
@JoinColumn(name="orderId", insertable=false, updatable=false)
private OrderItem orderItem;
}
当用户请求更新订单时,例如使用新OrderSubItem
添加新OrderItem
,将调用以下控制器:
@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
@Validated
@Slf4j
public class OrderController {
@NonNull
private final OrderService orderService;
@PutMapping
@ResponseStatus(HttpStatus.NO_CONTENT)
public void update(@Valid @RequestBody Order order) {
orderService.update(order);
}
}
当用户发送:
Order(id=1, orderItems=[
OrderItem(id=null, orderSubItems=[OrderSubItem(id=null, value=1)]),
OrderItem(id=1, orderSubItems=[OrderSubItem(id=1, value=2)])
验证在控制器处传递。然后,调用该服务:
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
@Transactional
public void update(Order order) {
List<OrderItem> orderItems = order.getOrderItems();
for (OrderItem orderItem : orderItems) {
List<OrderSubItem> orderSubItems = orderItem.getOrderSubItems();
List<OrderSubItem> newOrderSubItems = new ArrayList<OrderSubItem>();
Collections.reverse(orderSubItems);
for (OrderSubItem orderSubItem : orderSubItems) {
if (orderSubItem.getValue() == null) {
//skip it
} else {
orderSubItem.setIndex(newOrderSubItems.size());
newOrderSubItems.add(orderSubItem);
}
}
orderItem.setOrderSubItems(newOrderSubItems);
//also tried:
// orderItem.getOrderSubItems().clear();
// orderItem.getOrderSubItems().addAll(newOrderSubItems);
}
log.debug(order);
orderRepo.save(order);
}
在保存之前,我可以看到每个订单项目都有一个订单子项目。但保存失败,并显示一个ConstraintViolationException
,抱怨新添加的订单项的订单子项为 null。
为什么验证失败?我认为这可能与双向关系和列表的操纵有关。
更新 1
我创建了一个自定义@NotNull
验证器,以便我可以检查OrderItem
和 lo 的内容,并在持久验证期间看到它orderSubItems
为 null。
更新 2
经过更多的研究,我发现我添加到OrderItem
的任何@OneToMany
属性都将仅由一个 Hibernate 似乎创建的对象为 null。没错,看来 冬眠创造了一个OrderItem
.关系是否是双向的并不重要。列表是否纵也无关紧要。其他属性填充良好。现在,这是奇怪的部分:
如果我在Order
级别编写一个自定义验证器来检查其OrderItem
是否具有非空orderSubItems
并删除orderSubItems
上的NotNull
注释,它会毫无问题地通过验证和保存。
也就是说,Hibernate创建的OrderItem
似乎没有附加到Order
对象。这是Hibernate首先保存OrderItem
,然后从数据库中获取其id以便它可以保存其orderSubItems
的临时步骤吗?如果是这样,为什么它调用验证?
有时我更喜欢使用 DTO 来自定义和仅公开我需要的字段,而不是向我的 API 客户端公开我的实体。我认为当您在其他对象中有数据时,处理实体会更容易。
例如:
@Data
class OrderDTO {
private Long orderId;
private List<ItemDTO> items;
}
public void update(@Valid @RequestBody OrderDTO orderDTO) {
Order order = repository.load(orderDTO.getId());
for (OrderItem orderItem : order.getItems()) {
repository.delete(orderItem);
}
order.getItems().clear();
for (ItemDTO newItem : orderDTO.getItems()) {
OrderItem orderItem = new OrderItem(newItem.getFieldA(), newItem.getFieldB());
orderItem.setOrder(order);
repository.save(orderItem);
order.getItems().add(orderItem);
}
}