【问题标题】:How to prevent concurrency issue in UPDATE via iBatis如何通过 iBatis 防止 UPDATE 中的并发问题
【发布时间】:2012-09-18 18:44:35
【问题描述】:

我们的 Java EE Web 应用程序使用 iBatis (ORM) 执行数据库操作。数据库操作流程如下

流程 : JSP --->Action--->ServiceIMpl--->DaoImpl---->通过'IBatis 调用更新查询

注意:Action、Service 和 DAO 类是使用 Spring 2.5 的依赖注入技术实例化的。我们使用 Struts2。

问题:2 个并发用户搜索相同的记录,User1 更新 Attribute1,而 User2 更新 Attribute2。用户 1 首先单击“保存”,然后用户 2 单击保存(它们彼此跟随,相差几秒钟)。当我们看到数据库中的数据时,只有 User1 的更新存在。 Audit 列也只显示 User1 的更新。 User2对Attribute2的更新没有完成,同时没有抛出异常。

我们尝试在 iBatis 中使用开始事务和提交事务,围绕在 sqlmap xml 中定义的更新查询的调用。同样的问题仍然存在。我们也尝试删除这些事务命令,但问题仍然存在。

我们是否应该在更新期间做一些特殊/不同的事情来处理并发?像 iBatis 这样的 ORM 不会自己处理吗?

其他信息:进一步调查发现了以下信息。

当用户 1 和用户 2 点击记录时,将获取完整的数据以供查看。

现在,当 User1 和 User2 都更改了一些属性并单击保存(同时)时,假设首先更新 User1 的数据,然后在更新 User2 的数据时,User1 已经更新的数据被覆盖并丢失。

What approach is usually followed in such screens to handle such scenarios ?

【问题讨论】:

  • 这不是一个真正的 struts2 问题。不知道 iBatis 但奇怪的是没有抛出异常,如果可能的话增加日志记录级别。找出正在使用的锁定方案,考虑乐观锁定。
  • @Quaternion 我已经更新了这个问题,还有一些更新......

标签: spring concurrency struts2 ibatis


【解决方案1】:

您的问题不在于事务,而是在您修改某些内容之前,您必须确保自上次阅读以来没有人修改过。

在 Web 应用程序中,您应该使用 optimistic locking。基本上,您的表中有一个“版本”字段,其初始值为insert(通常为1),并随着每个update 递增。程序是:

  1. 从表中读取数据,包括版本字段

  2. 向客户端发送数据,包括版本字段

  3. 从客户端接收修改后的数据,包括版本字段unchanged

  4. 更新表并增加版本但前提是该行中的版本字段与从客户端收到的原始版本字段相同。也就是说,你在哪里

    update
    ... 
    where id = :id_from_client
    

    ,现在你应该这样做

    update 
    ..., 
    version = version + 1 
    where 
    id = :id_from_client and 
    version = :version_from_client
    
  5. 获取更新的行数(您通常可以直接从持久性框架的更新操作中获取此信息)。如果更新的行数为 1,则操作成功。如果更新的行数为 0,则应发出并发错误信号。

一个例子:

  1. User1 获取数据,版本 = 17

  2. User2 获取数据,版本 = 17

  3. User1 原子性(原子性来自单个 SQL 语句):

    • 检查版本是否 = 17
    • 更新数据
    • 将版本设置为 18

  4. User2 原子:

    • 检查版本是否 = 17
    • 因为版本实际上是 18,发出并发错误信号并中止操作,通知用户由于其他人修改了相同的数据,操作无法完成。

大多数当前的持久性框架都开箱即用地支持这一点,而且大多是自动的(例如,参见JPA @Version annotation)。 iBatis 似乎没有,但由于它非常接近 SQL,因此您自己实现它应该不会有很多问题。请参阅此博客条目以获取 some details about optimistic locking with iBatis

使用此方案,您不会丢失更新,唯一的问题是某些特定情况会使用户非常烦恼:假设您花了 5 分钟完成某个表格,但最后却被告知您的操作无法完成,您需要从头开始重试!在这少数情况下,您应该在乐观锁定之上实现一些更复杂的东西。我会使用某种远程资源租赁(链接??):

  1. 客户端向服务器请求租用

  2. 如果数据已租用给另一个客户端,则发出并发错误信号。

  3. 否则将租约授予客户端有限的时间

  4. 在租约到期之前,服务器授予客户端对数据的独占访问权限。客户端还可以请求续租(例如,通过 AJAX)。服务器可以接受或拒绝续订。 (租用时间和续订政策应由服务器根据正在处理的具体用例决定)。

  5. 租约到期(例如,这可以通过服务器上的计划任务来完成)。当租约到期时,数据应被视为“未锁定”,除非客户端获得新租约,否则服务器应拒绝对客户端的访问。

这样,您的用户可以在他们试图完成的任何冗长操作之前在他们开始之前通知他们正在修改相同的数据

【讨论】:

  • +1 您描述的租赁系统是服务级别的悲观锁定实现。这是最容易实现的系统之一(也是一个很好的系统)。一个更具挑战性的系统将是留下乐观锁定,如果发生冲突,则向用户提供足够的数据以在需要时协调情况。用户界面可能会变得相当棘手。另一个系统(由 JPA 通过侦听器实现)是注册每个感兴趣的客户并将所有权应用于记录,其中的转折点是实现了一个“yank”功能,可以让您窃取所有权。
  • 你是对的,只是说“悲观锁定”意味着对许多人(包括我自己)在数据库级别无限期地锁定。我更喜欢将类似于我描述的方法称为“租赁”,而将“悲观锁定”留给LOCK TABLE...SELECT ... FOR UPDATE 和那些东西。
  • 感谢您抽出宝贵时间提供如此详细的答案。在阅读乐观和悲观锁定概念时,我也在四处寻找 iBatis ORM 是否支持开箱即用的任何此类功能,但看起来不像 JPA ORM。
  • 我已经开始寻找选项,包括使用 Spring 中的 AOP 或使用 AspectJ 将乐观锁定功能作为可重用功能......欢迎任何指针/方向......
【解决方案2】:

Ibatis不执行锁定。看到你的场景乐观锁定可以解决问题。要实现乐观锁定,你可以有选择。 1.使用支持事务管理的框架,如org.springframework.jdbc.datasource。 DataSourceTransactionManager。它们提供传播行为和隔离级别。但是,您使用的数据源必须支持传播级别和隔离级别。Spring 提供以下隔离级别。 .ISOLATION_DEFAULT .ISOLATION_READ_UNCOMMITTED .ISOLATION_READ_COMMITTED .ISOLATION_REPEATABLE_READ .ISOLATION_SERIALIZABLE。 2.接下来你可以提供你自己的乐观锁实现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    • 1970-01-01
    • 2022-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多