【问题标题】:Prevent two users from submitting the same form data from separate sessions防止两个用户从不同的会话提交相同的表单数据
【发布时间】:2018-11-27 06:11:34
【问题描述】:

我的应用程序是基于表单的应用程序,用户可以在其中提出请求,将为该请求分配一个 ID,用户将填写一些详细信息,然后通过不同的阶段提交。多个用户可能是一个请求的一部分的问题,因此可能存在用户 A 和用户 B 同时查看表单的情况。用户A可能先提交,然后用户B提交,这会导致请求只显示用户B提交的信息。为了解决这个问题,我想出了两个解决方案:

-在我们将数据提交到数据库之前,我们会调用 get 来查看数据是否已经提交(我们在应用程序中使用状态。所以我们会检查“Stage # Pending”的状态)。

-我们为其他一些服务实现了 Redis 缓存。我可以添加类似

public submitRequest() {
  RMap<String, int> requestLockMap = redissonClient.getMap("requestLock");
  if(requestLockMap.get('request_ID') == 0) {
     requestLockMap.put('request_ID', 1);
     ...continue submitting request...
  }
}

这本质上是在 Redis 缓存中添加竞速条件。但是,我不知道这是否是这种事情的好习惯。有没有更好的选择?我使用 Spring 作为我们的 Java 框架,所以如果有任何解决方案,我愿意听取他们的意见。

【问题讨论】:

  • 如果你使用hibernate,它已经有内置版本检查是否是更新。你为什么不使用它。
  • @AshraffAliWahab 我们使用 sql server 定义自己的 SQL,Java 不是 hibernate 吗?
  • Hibernate 用于 Java 连接到任何 RDBMS。所以 SQL 服务器不是问题。
  • @AshraffAliWahab 是的,但是您不使用 Hibernate 框架定义查询过程吗?我们所有的存储过程都是在我们运行的sql server中定义的。我们可以将过程移植到 Java 并在考虑 Hibernate 的情况下对其进行编码,但这需要付出巨大的努力。或者我可以将 Hibernate 合并到我们的 Java 项目中并使用它来调用存储的 proc 名称吗?
  • 好的,知道了。那变化太大了。查看有关基于事务/会话的 SQL 服务器存储过程中的应用程序锁定的链接。 sqlteam.com/articles/…

标签: java race-condition


【解决方案1】:

为每个提交分配一个随机生成的 ID 会有什么缺点?

例如使用 UUID

UUID.randomUUID().toString()

您将相同请求 ID 的概率降低到统计意义上的 0。DB 冲突可被视为例外情况,因为这种情况不太可能发生。

使用这种方法,您还可以避免未经授权的用户可以使用先前的请求 ID 查看/编辑相邻信息的情况。

【讨论】:

    【解决方案2】:

    不要遵循您的第二个解决方案。正如你所说,它很可能会导致竞争条件。

    您的第一个建议是正确的。关键是在事务中进行更新,并在那里检查状态是否符合预期,以便您继续进行更新。

    状态类似于拥有版本 ID。更新条目时,版本 ID 会递增。因此,后续/并行编辑将具有无效的版本号或状态,并且不会继续。在您的情况下,如果状态不是待处理,您将不会继续进行更新。

    在事务中,您可以回滚并向用户返回错误消息,即请求已被用户 X 编辑。

    但是,如果有很多状态,您最好使用版本 ID 而不是状态。

    另一种方法是在更新语句的 where 子句中添加所需的状态,如 here 所示,以避免更新已修改的行。

    Hibernate 有一个内置机制来实现optimistic locking。详情请见this

    【讨论】:

      【解决方案3】:

      我建议在表中有一个时间戳字段,用户正在更新该字段。当您向用户显示表单时,请阅读时间戳并将其保持在会话中。更新时,请执行update table A set some_column = 'value', timestamp = now() where id = 1 and timestamp = 'whatever timestamp you read earlier' 之类的操作。然后查看更新语句的返回,如果为 1,则您更新了该行,如果为 0,则其他用户已经更新 - 向用户显示一些错误/通知。当然,您需要在每次更新期间更新时间戳字段。

      【讨论】:

        【解决方案4】:

        一种解决方案是使用自增整数字段。当您获取将具有一个值的记录并将其包含在表单信息中时。提交表单时,读取记录以查看它是否具有相同的值。如果是这样,请提交。如果不是,则拒绝并向用户发送一个错误,告诉他们他们太迟了。

        由于该字段在每次提交时都会自动递增,因此您无需执行任何操作,只需确保提交的表单包含该字段的相同值。

        【讨论】:

        • 这种方法对用户并不友好。对于用户很少的小型系统,这种情况很少发生,但考虑到数百个并发用户/系统
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-25
        • 2013-05-24
        相关资源
        最近更新 更多