2011-07-23 35 views
1

假设我们有以下的数据库结构和相应的网页:用户输入验证和数据库约束

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

相关部分从web页面(在伪代码):

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类将呈现form由来自t2行为optionselect元素,和input用于提交表单。

现在的问题,还假设网站的其他部分允许用户删除行从t2,则以下是可能的:

  1. 用户A请求含有上述形式的网页由行(1,“a”),(2,“b”)和(3,“c”)组成的t2,其中例如(1,“a”)意味着id_bar = 1和name =“a”。
  2. 用户A从select元件选择(1,“A”)
  3. 用户A提交表单
  4. 服务器侧脚本开始验证数据,看到它的有效进入if的真实分支声明(但尚未将新行插入数据库中)
  5. 某些其他用户B使用网站的某些其他部分从t2(即(1,“a”))中删除所选行
  6. 步骤5中的服务器脚本继续并尝试将数据插入到数据库中,但这会导致违反约束条件se id 1不再引用行t2

所以看起来服务器脚本也必须处理这种情况;即重新呈现表单并通知用户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(); 

这似乎是一个非常普遍的问题(例如,需要一个唯一的用户名),所以有像这些情况下,一些知名的标准溶液这里介绍?这样做的

回答

0

一种方法是从表中实际上没有delete记录,而是使用软删除将它们标记为已删除,这样的UI(和其他应用层)可以告诉它们不存在。

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

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