Проверка пользовательского ввода и ограничения базы данных - PullRequest
1 голос
/ 23 июля 2011

Предположим, у нас есть следующая структура базы данных и соответствующая веб-страница:

База данных состоит из двух таблиц, t1 и t2.Таблица t2 состоит из двух столбцов, один из которых называется id_bar, который является просто уникальным идентификатором, а другой называется name и используется для хранения некоторой строки, используемой пользовательским интерфейсом.Другая таблица, t1, имеет два столбца, один из которых называется id_foo, который является просто уникальным идентификатором, а другой - 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 будет отображать form, состоящий из элемента select со строками изt2 как option s и input, используемый для отправки формы.

Теперь к проблеме, кроме того, предположим, что какая-то другая часть веб-сайта позволяет пользователям удалять строки из t2, тогда возможно следующее:

  1. Пользователь A запрашивает веб-страницу, содержащую форму, описанную выше, с t2, состоящим из строк (1, "a"), (2, "b") и (3, "c") где, например, (1, "a") означает, что id_bar = 1 и name = "a".
  2. Пользователь A выбирает (1, "a")из элемента select
  3. Пользователь A отправляет форму
  4. Сценарий на стороне сервера начинает проверять данные и видит, что они действительны при вводе истинной ветвиif оператор (однако, еще не вставив новую строку в базу данных)
  5. Какой-то другой пользователь B удаляет выбранную строку из t2 (то есть (1, "a")), используя некоторыедругая часть веб-сайта
  6. Сервер-сценарий из шага 5 продолжается и пытается вставить данные в базу данных, но это приводит к нарушению ограничения, поскольку идентификатор 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();

Это кажется очень распространенной проблемой (например, требуется уникальное имя пользователя), поэтомуЕсть ли какое-то известное стандартное решение для подобных ситуаций, представленное здесь?

1 Ответ

0 голосов
/ 16 сентября 2011

Один из способов сделать это - на самом деле не delete записей из таблиц, а вместо этого использовать мягкое удаление , чтобы пометить их как удаленные, чтобы пользовательский интерфейс (и другие прикладные уровни) могли сказать, что они не являются там.

Инфраструктуры

ORM, такие как, например, Hibernate, решают проблему «параллельного изменения» с помощью поля версия . Затем любые обновления в конечном итоге приводят к тому, что часть их предложения where выглядит как where myVersion = dbVersion, и, если затем, ловят исключения и выясняют, почему все меняется.

Это препятствует тому, чтобы изменения проходили вслепую, а также делает это атомарным образом, так что БД контролирует атомарность транзакции, а не код приложения. (блокировки в коде приложения грязные)

...