纯Java/JSF实现的重复提交预防方案

14

我们在WebSphere v8.5上使用JSF 2.0和几个组件库,如PrimeFaces 4.0、Tomahawk 2.0、RichFaces等。

我正在寻找一种通用机制,以避免在刷新页面或再次单击提交按钮时重新提交表单。我有许多具有不同场景的应用程序。

目前,我考虑在 onclick属性中使用JavaScript禁用按钮,但这并不令人满意。我正在寻找一个纯Java实现来实现此目的,类似于Struts2的<s:token>


Ajax是否解决了表单重新提交的问题?因为只有页面的一部分被渲染,而不是整个页面或重定向。 - Sarz
禁用 onClick 事件可能不足以实现目的,需要使用延迟计时器来解决问题。 - Nassim MOUALEK
是的,@NassimMOUALEK,我成功地实现了禁用按钮,直到ajax完成其渲染。 - Sarz
当用户在按钮上快速点击多次时,需要延迟提交以决定是否应该提交,这并不是我所考虑的问题。 - Nassim MOUALEK
正确,但我认为网页JS比人类(一遍又一遍地点击)要快得多。首先,在单击时执行的操作是禁用按钮,然后当ajax完成其周期时,将启用此按钮。因此,通过这种方式,不会发生重叠事件。 - Sarz
我们在BNP客户端遇到了问题,他们只使用IE浏览器,并且相信在IE用户可以同时点击两次(无论页面是什么),我们在Hibernate事务级别处理了这个问题,但是它可以通过延迟来修复,就像我所提到的。 - Nassim MOUALEK
3个回答

18
我希望您能够在页面刷新时避免表单重新提交的通用机制。有至少两种解决方案,不能同时使用:
  1. 同步提交后重定向。这样刷新将仅重新执行重定向的GET请求,而不是最初的请求。缺点:您无法再使用请求范围来为最终用户提供任何反馈。JSF 2.0通过提供新的flash作用域解决了这个问题。另请参阅如何在重定向页面中显示faces消息

  2. 在后台异步执行POST(使用ajax)。这样,刷新将仅重新执行初始打开表单的GET请求。您只需要确保那些表单仅由GET请求打开,即永远不应该通过POST进行页面导航(这本身就是一个糟糕的设计)。另请参阅何时使用h:outputLink而不是h:commandLink?


或者当提交按钮再次被点击时

针对这种情况,基本上也有至少两种解决方案,必要时可以结合使用:

  1. 在提交期间和/或成功提交后,禁用结束用户按下提交按钮即可。具体的功能和设计要求决定了多种方法。您可以使用JavaScript在提交期间禁用按钮。您可以使用JSF的disabledrendered属性在提交后禁用或隐藏按钮。另请参见如何在JSF 2中执行双击预防措施。在处理ajax请求期间,您还可以使用覆盖窗口来阻止任何结束用户交互。PrimeFaces具有<p:blockUI>用于此目的。

  2. 在服务器端验证新添加实体的唯一性。如果出于技术原因而非功能原因绝对要避免重复,则这样做更加健壮。这很简单:在相关DB列上放置一个UNIQUE约束。如果违反该约束,则DB(以及类似JPA的DB交互框架)将引发约束冲突异常。最好与自定义JSF验证器结合使用,该验证器通过在正式进行SELECT之前执行对该列的验证,并检查是否未返回任何记录来验证输入。JSF验证器允许您以友好的面孔消息显示问题。另请参见针对数据库验证电子邮件格式和唯一性等。


我在我的所有应用程序的表单中都使用<f:ajax />,但是这个FlashScope会让页面重定向/重新加载。我该如何解决这个问题? - Sarz
在“另请参阅”链接中有一个示例。 - BalusC
在服务器端验证唯一性可能会变得复杂。例如,我正在向我的朋友发送短信。在这种情况下,没有逻辑或约束来检查唯一性。也许,缓存和匹配请求会给你一个提示,但即使我们忽略匹配成本,仍可能存在业务情况,其中具有相同值的请求是正常的一部分。 - Mashrur

3

当我在h:commandButton中放置<f:ajax />时,现在它不会重新提交表单。它是否满足要求? - Sarz
问题在于我们需要根据我们的情况启用和禁用双重提交。 - Sarz

0

<!--Tag to show message given by bean class -->
<p:growl id="messages" />

<h:form>
  <h:inputText a:placeholder="Enter Parent Organization Id" id="parent_org_id" value="#{orgMaster.parentOrganization}" requiredMessage="Parent org-id is required" />


  <h:commandButton style="margin-bottom:8px;margin-top:5px;" class="btn btn-success btn-block " value="Save" type="submit" action="#{orgMaster.save}" onclick="resetform()" />
</h:form>

    public String save() {
    FacesContext context = FacesContext.getCurrentInstance();
    context.getExternalContext().getFlash().setKeepMessages(true); //This keeps the message even on reloading of page

FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Your submission is successful.", " ")); // To show the message on clicking of submit button


return "organizationMaster?faces-redirect=true"; // to reload the page with resetting of all fields of the form.. here my page name is organizationMaster...you can write the name of form whose firlds you want to reset on submission

}

这将打印成功消息并重置表单。 不要忘记在前端的form标签开始前编写<p:growl id="messages" />。 - Vinod Chauhan
您可以编辑您的答案并在其中添加此相关评论。 - Kukeltje

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接