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