如何通过iBatis防止UPDATE中的并发问题

yat*_*gan 2 concurrency spring ibatis struts2

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

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

注意:Action,Service和DAO类使用Spring 2.5的依赖注入技术进行实例化.我们使用Struts2.

问题:2个并发用户搜索相同的记录,User1更新Attribute1,而User2更新Attribute2.User1首先点击"保存",然后User2点击保存(它们相互跟随,差几秒).当我们在数据库中看到数据时,只存在User1的更新.审核列还仅显示User1的更新.User2对Attribute2的更新未完成,同时不会抛出异常.

我们尝试在iBatis中使用begin transaction和commit transaction来围绕sqlmap xml中定义的更新查询的调用.同样的问题仍然存在.我们还尝试删除这些事务命令,但问题仍然存在.

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

附加信息:进一步调查显示以下信息.

当User1和User2单击某条记录时,将获取完整的数据以供查看.

现在,当User1和User2都更改了少量属性并单击save(并发)时,首先更新User1的数据,然后在更新User2的数据时,User1的已更新数据被覆盖并丢失.

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

gpe*_*che 9

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

在Web应用程序中,您应该使用乐观锁定.基本上,您的表中有一个"version"字段,其初始值为insert(tipical 1),并随每个字段递增update.程序是:

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

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

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

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

    update
    ... 
    where id = :id_from_client
    
    Run Code Online (Sandbox Code Playgroud)

    ,现在你应该这样做

    update 
    ..., 
    version = version + 1 
    where 
    id = :id_from_client and 
    version = :version_from_client
    
    Run Code Online (Sandbox Code Playgroud)
  5. 获取更新行的数量(通常可以从持久性框架的更新操作中获取此信息).如果更新的行数为1,则操作成功.如果更新的行数为0,则应表示并发错误.

一个例子:

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

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

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

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

  4. User2原子地:

    • 检查版本是否= 17
    • 由于版本实际上是18,表示并发错误并中止操作,通知用户由于其他人修改相同数据而无法完成操作.

大多数当前的持久性框架都支持开箱即用,并且主要是自动支持(例如,参见JPA @Version注释).iBatis似乎没有它,但由于它非常接近SQL,你自己实现它不应该有很多问题.有关使用iBatis进行乐观锁定的一些详细信息,请参阅此博客条目.

使用这个方案,你不会得到丢失的更新,唯一的问题是一些特定的情况会使用户烦恼很多:想象你花了5分钟完成一些表格只是为了在最后被告知你的操作无法完成和你需要从一开始就重试!! 在这几种情况下,您应该在乐观锁定之上实现更复杂的东西.我会使用某种远程资源租赁(链接??):

  1. 客户端向服务器请求数据租约.

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

  3. 否则,在有限的时间内向客户授予租约.

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

  5. 租约到期(例如,这可以通过服务器上的计划任务完成).当租约到期时,数据应被视为"未锁定",并且服务器应拒绝访问客户端,除非客户端被授予新租约.

通过这种方式,您的用户可以在他们尝试完成任何冗长的操作之前,通知他们正在修改相同的数据.