用户输入验证和数据库约束

时间:2011-07-23 00:36:32

标签: validation concurrency foreign-keys atomicity

假设我们有以下数据库结构和附带的网页:

该数据库由两个表t1t2组成。表t2由两列组成,一列称为id_bar,它只是一个唯一的id,另一列称为name,用于存储ui使用的一些字符串。另一个表t1有两列,一个名为id_foo,只是一个唯一的ID,另一个名为id_bar,必须指向t2中的某一行(即这是外键)。

网页的相关部分是(伪代码):

Form form = new Form();
form.build_and_add_select_from_db_table("t2", "field1");
form.add_submit_button();

if (request.is_post() && form.is_valid(request.get_post_data())) {
    Add a new row to t1, using the data from the request
    response.redirect(<somewhere>);
}

print form.render(); // this will display validation errors as well

因此,Form类将呈现formselect元素组成,其中t2行为option s,input用于提交表单。

现在针对这个问题,进一步假设网站的其他部分允许用户从t2删除行,那么可以使用以下内容:

  1. 用户A请求包含上述表格的网页,其中t2由行(1,“a”),(2,“b”)和(3,“c”)组成,其中例如: (1,“a”)表示id_bar = 1且name =“a”。
  2. 用户A从select元素
  3. 中选择(1,“a”)
  4. 用户A提交表单
  5. 服务器端脚本开始验证数据,并发现它有效进入if语句的真正分支(但是,尚未将新行插入数据库)
  6. 其他一些用户B使用网站的其他部分从t2删除所选行(即(1,“a”))
  7. 步骤5中的服务器脚本继续并尝试将数据插入数据库,但这会导致约束违规,因为id 1不再引用t2中的行
  8. 所以似乎服务器脚本也必须处理这种情况;也就是说,重新呈现表单并通知用户A所选选项不再可用。

    还存在由这种非原子性引起的其他类似情况,例如:如果我们有一个名为user的表,其中每个用户都应该拥有唯一的用户名,通过与上述相同的推理,我们不能简单地说:

    if (check if the username already exists in the database)
      display_error();
    else
      insert_new_user_into_database();
    

    一种解决方案(在两种情况下)都是乐观地尝试插入新行,然后以某种方式确定违反了什么约束,以及为什么,以及通过(我猜)解析错误消息和错误代码,通知用户出了什么问题。然而,这感觉非常混乱:

    try {
      if (request.is_post() && form.is_valid(request.get_post_data())) {
        Add a new row to t1, using the data from the request
        response.redirect(<somewhere>);
    } catch (DatabaseException e) {
      if (e.is_some_constraint_violation()) {
         // Find out what field caused the violation
         // and generate an appropriate error message,
         // possibly also removing alternatives from some form fields, 
         // it will rethrow the exception or something if it can't
         // figure out what happened.
         form.handle_database_constraint_violation(e);
      } else
         throw;
    }
    

    另一种解决方案可能是某种锁定?

    if (request.is_post())
      lock_everything();
    
    Build form ...
    
    if (request.is_post() && form.is_valid(request.get_post_data())) {
      Insert the new row into the database
      unlock_everything();
      response.redirect(<some other page>);
    } else
      unlock_everything();
    

    这似乎是一个非常常见的问题(例如需要一个唯一的用户名),那么这里介绍的情况是否有一些众所周知的标准解决方案?

1 个答案:

答案 0 :(得分:0)

这样做的一种方法是不从表中实际记录delete,而是使用软删除将其标记为已删除,以便UI(和其他应用程序层)可以分辨他们不在那里。

ORM框架(例如Hibernate)通过使用版本字段来处理这种“并发修改”问题。然后,任何更新最终都会使where子句的一部分看起来像where myVersion = dbVersion,如果然后捕获异常并找出原因发生变化的原因。

这可以防止改变只是盲目地进行,并且还以原子方式进行,以便DB控制事务的原子性,而不是应用程序代码。 (锁定应用程序代码很乱)