我应该将实体的 ID 放在 URL 中,还是作为隐藏字段放入表单中?

2022-09-02 10:41:16

我认为就REST而言,ID应该放在URL中,如下所示:

https://example.com/module/[ID]

然后我在那个URL上调用GET,PUT,DELETE。我认为这很清楚。在Spring MVC控制器中,我会获得带有@PathVariable的ID。工程。

现在,我对Spring MVC的实际问题是,如果我这样做,我必须不要将ID作为表单的一部分(以及),Spring会发出类型的警告

Skipping URI variable 'id' since the request contains a bind value with the same name.

否则。而且只发送一次也很有意义,对吧?如果他们不匹配,你会怎么做??

这很好,但是我确实有一个用于我的表单支持bean的自定义验证器,它需要知道ID!(它需要检查某个唯一名称是否已用于其他实体实例,但不能不知道提交的表单的 ID)。

我还没有找到一种好方法来告诉验证者来自@PathVariable的ID,因为验证甚至发生在我的控制器方法中的代码执行之前。

您将如何解决这一困境?

这是我的控制器(已修改):

@Controller
@RequestMapping("/channels")
@RoleRestricted(resource = RoleResource.CHANNEL_ADMIN)
public class ChannelAdminController
{
    protected ChannelService channelService;
    protected ChannelEditFormValidator formValidator;

    @Autowired
    public ChannelAdminController(ChannelService channelService, ChannelEditFormValidator formValidator)
    {
        this.channelService = channelService;
        this.formValidator = formValidator;
    }

    @RequestMapping(value = "/{channelId}/admin", method = RequestMethod.GET)
    public String editChannel(@PathVariable Long channelId, @ModelAttribute("channelForm") ChannelEditForm channelEditForm, Model model)
    {
        if (channelId > 0)
        {
            // Populate from persistent entity
        }
        else
        {
            // Prepare form with default values
        }
        return "channel/admin/channel-edit";
    }

    @RequestMapping(value = "/{channelId}/admin", method = RequestMethod.PUT)
    public String saveChannel(@PathVariable Long channelId, @ModelAttribute("channelForm") @Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
    {
        try
        {
            // Has to validate in controller if the name is already used by another channel, since in the validator, we don't know the channelId
            Long nameChannelId = channelService.getChannelIdByName(channelEditForm.getName());
            if (nameChannelId != null && !nameChannelId.equals(channelId))
                result.rejectValue("name", "channel:admin.f1.error.name");
        }
        catch (EmptyResultDataAccessException e)
        {
            // That's fine, new valid unique name (not so fine using an exception for this, but you know...)
        }

        if (result.hasErrors())
        {
            return "channel/admin/channel-edit";
        }

        // Copy properties from form to ChannelEditRequest DTO
        // ...

        // Save
        // ...

        redirectAttributes.addFlashAttribute("successMessage", new SuccessMessage.Builder("channel:admin.f1.success", "Success!").build());
        // POST-REDIRECT-GET
        return "redirect:/channels/" + channelId + "/admin";
    }


    @InitBinder("channelForm")
    protected void initBinder(WebDataBinder binder)
    {
        binder.setValidator(formValidator);
    }
}

答案 1

我想我终于找到了解决方案。

事实证明,Spring也绑定路径变量以形成豆子!我没有在任何地方找到这个文档,也不会预料到它,但是当尝试重命名path变量时,就像@DavidW建议的那样(我本来以为只会在我的控制器方法中产生局部影响),我意识到有些东西被破坏了,因为前面提到的。

因此,基本上,解决方案是在表单支持对象上也具有ID属性,但不要在HTML表单中包含隐藏的输入字段。这样,Spring将使用路径变量并将其填充到表单上。甚至可以跳过控制器方法中的本地参数。@PathVariable


答案 2

我认为,解决这个问题的最干净方法是让数据库处理重复项:向数据库列添加唯一约束。(或 JPA 通过添加一个 )但是,您仍然必须捕获数据库异常并将其转换为用户友好的消息。@UniqueConstraint

通过这种方式,您可以保持弹簧MVC验证器的简单性:仅验证字段,而无需查询数据库。