我认为就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);
}
}
答案 0 :(得分:6)
我想我终于找到了解决方案。
事实证明,Spring将路径变量绑定到形成bean!我没有在任何地方找到这个记录,并且没有预料到它,但是当试图重命名路径变量时,就像@DavidW建议的那样(我希望在我的控制器方法中只有本地效果),我意识到因为前面提到过,有些东西被打破了。
因此,基本上,解决方案是在表单支持对象上具有ID属性,但不包括HTML表单中的隐藏输入字段。这样Spring将使用路径变量并在表单上填充它。甚至可以跳过控制器方法中的本地@PathVariable
参数。
答案 1 :(得分:5)
我认为,最简单的解决方法是让数据库处理重复项:向数据库列添加唯一约束。 (或通过添加@UniqueConstraint
来JPA)
但是你仍然需要捕获数据库异常并将其转换为用户友好的消息。
通过这种方式,您可以保持spring MVC验证器简单:只验证字段,而无需查询数据库。
答案 2 :(得分:2)
你所说的是正确的设计rest api的正确方法是提到路径变量中的资源id如果你从swagger看一些例子现在作为open api你可以在那里找到类似的例子
对你来说,正确的解决方案是使用像这样的自定义验证器
ContentControl.Content = new Circular();
这将确保所有字段都经过验证,您无需在表单中传递id。
答案 3 :(得分:2)
您是否可以通过为URI模板变量使用不同的名称来简单地消除2(URI模板变量与参数)的歧义?
@RequestMapping(value = "/{chanId}/admin", method = RequestMethod.PUT)
public String saveChannel(@PathVariable Long chanId, @ModelAttribute("channelForm") @Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
[...]