Spring MVC:验证嵌套对象的 ID
Spring MVC: Validate id of nested object
主要问题
在我使用 Spring/Hibernate/Thymeleaf 实现的网络应用程序中,我有两个域对象,Owner 和 Pet。 Pet 对象有一个(可选的)Owner 字段。在通过 Web 表单更新宠物期间,用户可以输入(在数据库中)存在或不存在的所有者 ID。
当用户使用现有所有者 ID 更新宠物时,现有所有者对象会被发现并与宠物相关联。
但是,当用户使用数据库中不存在的所有者 ID 更新宠物时,returned 宠物对象(在“POST”控制器方法中)具有空所有者对象。此外,由于 pet 域中的 owner 字段可以为 null,因此不会引发验证错误。
最终,这可能会导致用户提交错误的值,认为他们已经添加了一个所有者,只是为了宠物的更新实际发生在一个空所有者(如果没有正确捕获)。
我的问题是,在执行父对象更新之前验证嵌套对象的 ID 存在的推荐方法是什么。
进一步思考:
- 我看到一个相关问题:spring mvc nested model validation,建议添加自定义验证器。在我的例子中,添加一个自定义验证器,它将在 PetController 中 运行 并验证 Pet/Owner 对象,我认为,这是行不通的。 returned 宠物对象包含一个空的所有者对象,这是一个完全有效的状态。我需要在数据绑定期间进行验证。一旦我们进入控制器,post-binding,所有者对象为 null 并且不会发生表单值验证。
- 我通过添加自定义格式化程序(从 String 到 Owner)对此做了一些工作。如果找不到所有者 ID,此格式化程序将 return 一个新的所有者对象,其 ID 为“Not Found”(或在这种情况下为某些 numeric/integer 等价物)。在我的控制器中,我可以检查具有“错误 ID”的所有者并引发错误。但是,不 return 对象的默认行为似乎更安全,因为我的自定义格式化程序实际上 return 是一个无效对象(这可能会导致其他问题?)。
代码
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
@Id
private int id;
private String name;
@ManyToOne
@Valid
private Owner owner;
}
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Owner {
@Id
@NotNull
@NonNull
private int id;
private String firstName;
private String lastName;
}
@Controller
public class PetController {
@Autowired
PetRepository petRepository;
@RequestMapping("editPet")
@GetMapping
public String editPet(Model model) {
Pet pet = petRepository.getById(1);
model.addAttribute("pet", pet);
return "editPet";
}
@RequestMapping("updatePet")
@PostMapping
public String updatePet(@Validated Pet pet, BindingResult bindingResult) {
petRepository.save(pet); // if the user submits an invalid owner id, the returned pet will have a null owner
// object, but no error raised.
return "editPet";
}
}
}
我想出了一种干净的方法来完成上述操作,张贴在这里以防对其他人有帮助。
解决方案是利用 Spring 的类型 conversion。从文档中获得的两个要点是,首先,要创建一个转换器,我们只需要实现 Converter 接口。其次,要报告无效的源值(例如我上面的问题陈述中的错误所有者 ID),应该引发 IllegalArgumentException。
转换期间引发的任何 IllegalArgumentExceptions 都将在控制器方法的 BindingResult 中报告。这意味着不需要额外的 code/effort 来报告控制器中的非法值。
StringToOwner 转换器的实现:
首先,检查存储库中是否存在具有给定 ID 的可选所有者。如果没有具有给定 id 的所有者,则会引发异常。不需要检查是否为空;根据 documentation,每次调用转换器时,保证源参数不为空。
@Component
public class StringToOwner implements Converter<String, Owner> {
@Autowired
OwnerRepository ownerRepository;
@Override
public Owner convert(String s) {
Optional<Owner> optOwner = ownerRepository.findById(Integer.parseInt(s));
if (optOwner.isEmpty()) {
throw new IllegalArgumentException("Illegal owner id");
}
return optOwner.get();
}
}
随后,我们可以编辑控制器来检查绑定错误:
@RequestMapping("updatePet")
@PostMapping
public String updatePet(@Validated Pet pet, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("pet", pet);
return "editPet"; // Errors can be reported in the generated page
}
petRepository.save(pet);
return "success";
}
主要问题
在我使用 Spring/Hibernate/Thymeleaf 实现的网络应用程序中,我有两个域对象,Owner 和 Pet。 Pet 对象有一个(可选的)Owner 字段。在通过 Web 表单更新宠物期间,用户可以输入(在数据库中)存在或不存在的所有者 ID。
当用户使用现有所有者 ID 更新宠物时,现有所有者对象会被发现并与宠物相关联。 但是,当用户使用数据库中不存在的所有者 ID 更新宠物时,returned 宠物对象(在“POST”控制器方法中)具有空所有者对象。此外,由于 pet 域中的 owner 字段可以为 null,因此不会引发验证错误。
最终,这可能会导致用户提交错误的值,认为他们已经添加了一个所有者,只是为了宠物的更新实际发生在一个空所有者(如果没有正确捕获)。
我的问题是,在执行父对象更新之前验证嵌套对象的 ID 存在的推荐方法是什么。
进一步思考:
- 我看到一个相关问题:spring mvc nested model validation,建议添加自定义验证器。在我的例子中,添加一个自定义验证器,它将在 PetController 中 运行 并验证 Pet/Owner 对象,我认为,这是行不通的。 returned 宠物对象包含一个空的所有者对象,这是一个完全有效的状态。我需要在数据绑定期间进行验证。一旦我们进入控制器,post-binding,所有者对象为 null 并且不会发生表单值验证。
- 我通过添加自定义格式化程序(从 String 到 Owner)对此做了一些工作。如果找不到所有者 ID,此格式化程序将 return 一个新的所有者对象,其 ID 为“Not Found”(或在这种情况下为某些 numeric/integer 等价物)。在我的控制器中,我可以检查具有“错误 ID”的所有者并引发错误。但是,不 return 对象的默认行为似乎更安全,因为我的自定义格式化程序实际上 return 是一个无效对象(这可能会导致其他问题?)。
代码
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
@Id
private int id;
private String name;
@ManyToOne
@Valid
private Owner owner;
}
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Owner {
@Id
@NotNull
@NonNull
private int id;
private String firstName;
private String lastName;
}
@Controller
public class PetController {
@Autowired
PetRepository petRepository;
@RequestMapping("editPet")
@GetMapping
public String editPet(Model model) {
Pet pet = petRepository.getById(1);
model.addAttribute("pet", pet);
return "editPet";
}
@RequestMapping("updatePet")
@PostMapping
public String updatePet(@Validated Pet pet, BindingResult bindingResult) {
petRepository.save(pet); // if the user submits an invalid owner id, the returned pet will have a null owner
// object, but no error raised.
return "editPet";
}
}
}
我想出了一种干净的方法来完成上述操作,张贴在这里以防对其他人有帮助。
解决方案是利用 Spring 的类型 conversion。从文档中获得的两个要点是,首先,要创建一个转换器,我们只需要实现 Converter 接口。其次,要报告无效的源值(例如我上面的问题陈述中的错误所有者 ID),应该引发 IllegalArgumentException。
转换期间引发的任何 IllegalArgumentExceptions 都将在控制器方法的 BindingResult 中报告。这意味着不需要额外的 code/effort 来报告控制器中的非法值。
StringToOwner 转换器的实现:
首先,检查存储库中是否存在具有给定 ID 的可选所有者。如果没有具有给定 id 的所有者,则会引发异常。不需要检查是否为空;根据 documentation,每次调用转换器时,保证源参数不为空。
@Component
public class StringToOwner implements Converter<String, Owner> {
@Autowired
OwnerRepository ownerRepository;
@Override
public Owner convert(String s) {
Optional<Owner> optOwner = ownerRepository.findById(Integer.parseInt(s));
if (optOwner.isEmpty()) {
throw new IllegalArgumentException("Illegal owner id");
}
return optOwner.get();
}
}
随后,我们可以编辑控制器来检查绑定错误:
@RequestMapping("updatePet")
@PostMapping
public String updatePet(@Validated Pet pet, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("pet", pet);
return "editPet"; // Errors can be reported in the generated page
}
petRepository.save(pet);
return "success";
}