Обычно, когда вы изучаете оптимистическую блокировку, вы также используете такую библиотеку, как Hibernate. или другую реализацию JPA с поддержкой @Version
.
Пример можно было бы прочитать так:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
@Version
private Long version;
}
хотя очевидно, что нет смысла добавлять аннотацию @Version
, если вы не используете фреймворк, который это поддерживает.
Тогда DDL может быть
CREATE TABLE people (
person_id PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, # } I realize these column defs are not valid but this is just pseudo-code
age INT NOT NULL,
color_id FOREIGN KEY (colors) NOT NULL, # Say we also have a colors table and people has a 1:1 relationship with it
version BIGINT NOT NULL
);
Что происходит с версией?
- Каждый раз перед сохранением объекта вы проверяете, является ли версия, хранящаяся в базе данных, той версией, которую вы знаете.
- Если это так, сохраните данные с версией, увеличенной на единицу.
Чтобы выполнить оба шага, не рискуя тем, что другой процесс изменит данные между обоими шагами, это обычно выполняется с помощью оператора типа
UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;
После выполнения оператора вы проверяете, обновили ли вы строку или нет. Если вы это сделали, никто другой не изменил данные с тех пор, как вы их прочитали, иначе кто-то другой изменил данные. Если кто-то изменил данные, вы обычно получите OptimisticLockException
от библиотеки, которую вы используют.
Это исключение должно привести к отмене всех изменений и перезапуску процесса изменения значения, поскольку условие, при котором объект должен был быть обновлен, может больше не применяться.
Так что столкновения нет:
- Процесс А читает Человек
- Процесс A записывает Person, тем самым увеличивая версию
- Процесс B читает "Человек"
- Процесс B пишет Person, тем самым увеличивая версию
Столкновение:
- Процесс А читает Человек
- Процесс B читает "Человек"
- Процесс A записывает Person, тем самым увеличивая версию
- Процесс B получает исключение при попытке сохранить, поскольку версия изменилась с момента чтения Person
Если Color - это другой объект, вы должны поместить туда версию по той же схеме.
Что не является оптимистической блокировкой?
- Оптимистическая блокировка - это не волшебство для объединения конфликтующих изменений. Оптимистическая блокировка просто предотвратит случайную перезапись процессами изменений другим процессом.
- Оптимистическая блокировка на самом деле не является блокировкой DB. Он просто работает, сравнивая значение столбца версии. Вы не запрещаете другим процессам доступ к каким-либо данным, поэтому ожидайте, что вы получите
OptimisticLockException
s
Какой тип столбца использовать в качестве версии?
Если к вашим данным обращается много разных приложений, лучше всего использовать столбец, автоматически обновляемый базой данных. например для MySQL
version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
таким образом приложения, реализующие оптимистическую блокировку, будут замечать изменения, сделанные «глупыми» приложениями.
Если вы обновляете сущности чаще, чем разрешение TIMESTAMP
или его интерпретация на языке Java, этот подход может не обнаружить определенные изменения. Кроме того, если вы позволите Java генерировать новый TIMESTAMP
, вам необходимо убедиться, что все машины, на которых запущены ваши приложения, находятся в идеальной временной синхронизации.
Если все ваши приложения могут быть изменены на целочисленную, длинную, ... версию, как правило, хорошее решение, поскольку она никогда не будет страдать от других установленных часов ;-)
Есть и другие сценарии. Вы могли бы, например, использовать хэш или даже случайным образом генерировать String
каждый раз, когда строка должна быть изменена. Важно то, что вы не повторяете значения, пока какой-либо процесс хранит данные для локальной обработки или внутри кеша, поскольку этот процесс не сможет обнаружить изменение, посмотрев на столбец версии.
В крайнем случае вы можете использовать значение всех полей как версию. Хотя в большинстве случаев это будет наиболее затратным подходом, это способ получить аналогичные результаты без изменения структуры таблицы. Если вы используете Hibernate, есть @OptimisticLocking
-аннотация для обеспечения соблюдения это поведение. Используйте @OptimisticLocking(type = OptimisticLockType.ALL)
в классе сущности, чтобы сбой произошел, если какая-либо строка изменилась после того, как вы прочитали сущность, или @OptimisticLocking(type = OptimisticLockType.DIRTY)
, чтобы просто потерпеть неудачу, когда другой процесс также изменил поля, которые вы также изменили.
person
TheConstructor
schedule
14.02.2014