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 存在的推荐方法是什么。

进一步思考:

代码

@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";
    }