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



我认为就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 的情况下不能检查(。

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

您将如何解决这一困境?

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

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

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

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

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

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

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

您不能简单地通过对 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)
{
[...]

你说的是正确的 设计 rest API 的正确方法是在路径变量中提及资源 ID,如果您现在查看 swagger 中的一些示例作为开放 API,您可以在那里找到类似的例子

对您来说,正确的解决方案是使用这样的自定义验证器

import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile){
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}       
}           
}

这将确保所有字段都经过验证,并且您无需在表单中传递 id。

最新更新