如何在保存之前避免实体中包含元素的列表但在持续时间失败?
How to avoid a List in entity containing element before save but failing at persist time?
我需要允许用户编辑订单,包括在这些订单项下添加新的订单项和子项。我相应地为三个实体建模,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;
}
当用户请求更新订单时说要添加新的 OrderItem
和新的 OrderSubItem
s,调用以下控制器:
@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
抱怨新添加的订单项的订单子项为空。
为什么验证失败?我想这可能与双向关系和列表的操作有关。
更新 1
我创建了一个自定义的 @NotNull
验证器,这样我就可以检查 OrderItem
的内容,你瞧,它 orderSubItems
在持续验证期间为空。
更新 2
经过更多研究,我发现我添加到 OrderItem
中的任何 @OneToMany
属性 都只会被 Hibernate 似乎创建的一个对象设为 null。没错,看起来 Hibernate 创建了一个 OrderItem
。关系是否是双向的并不重要。列表是否被操纵也无关紧要。其他属性填充良好。现在,这是奇怪的部分:
如果我在 Order
级别编写自定义验证器以检查其 OrderItem
是否具有非空 orderSubItems
并删除 NotNull
orderSubItems
上的注释,它顺利通过了验证和保存。
也就是说,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);
}
}
我需要允许用户编辑订单,包括在这些订单项下添加新的订单项和子项。我相应地为三个实体建模,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;
}
当用户请求更新订单时说要添加新的 OrderItem
和新的 OrderSubItem
s,调用以下控制器:
@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
抱怨新添加的订单项的订单子项为空。
为什么验证失败?我想这可能与双向关系和列表的操作有关。
更新 1
我创建了一个自定义的 @NotNull
验证器,这样我就可以检查 OrderItem
的内容,你瞧,它 orderSubItems
在持续验证期间为空。
更新 2
经过更多研究,我发现我添加到 OrderItem
中的任何 @OneToMany
属性 都只会被 Hibernate 似乎创建的一个对象设为 null。没错,看起来 Hibernate 创建了一个 OrderItem
。关系是否是双向的并不重要。列表是否被操纵也无关紧要。其他属性填充良好。现在,这是奇怪的部分:
如果我在 Order
级别编写自定义验证器以检查其 OrderItem
是否具有非空 orderSubItems
并删除 NotNull
orderSubItems
上的注释,它顺利通过了验证和保存。
也就是说,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);
}
}