Эту вещь трудно объяснить, она связана с некоторыми особенностями предметной области. это мне не разрешено обсуждать, и я не могу копировать и вставлять точный код. Я постараюсь изложить свои мысли как можно более понятно на некоторых репрезентативных примерах.
Коротко говоря, система состоит из корневого объекта, назовем его объектом MainDocument. Вокруг этого объекта вращаются несколько объектов. Сущность MainDocument имеет состояние. Назовем это состояние «MainDocumentState».
public class MainDocument {
@OneToOne
@JoinColumn(name = "document_state_id")
MainDocumentState state;
@Version
long version = 0L;
}
Доступно около 10 состояний, но в этом примере мы сосредоточимся на двух из них. Давайте назовем их ReadyForAuthorization и Authorized.
Это все, что вам нужно знать для примера.
О технологии, которые мы используем:
- Spring
- GWT Webapp
- Java 1.6
- Гибернация
- JPA
- БД Oracle.
Есть раздел системы, который является критическим и обрабатывает большую часть входящего трафика. Назовем этот раздел «разделом авторизации». В этом разделе мы отправляем информацию через SOAP WS, предоставленную Таможенной и пограничной службой нашей страны, для авторизации основного документа на таможне.
Код выглядит следующим образом:
Код выглядит следующим образом: п>
@Transactional
public void authorize(Integer mainDocId) {
MainDocument mainDocument = mainDocumentService.findById(mainDocId);
// if document is not found, an exception is thrown.
Assert.isTrue(mainDocument.notAutorized(), "The document is already authorized");
// more business logic validations happen here. This validations are not important for the topic discussed here. They make sure that the document meets some basic preconditions.
try {
Transaction aTransaction = transactionService.newTransaction(); // creates a transaction, an entity stored in the database that keeps track of all the authorization service calls
try {
Response response = wsAuthroizationService.sendAuthorization(mainDocument.getId(), mainDocument.getAuthorizationId()); // take into account that sometimes this call can take between 2-4 minutes.
catch (Exception e) {
aTransaction.failed();
transactionService.saveOrUpdate(aTransaction);
throw e;
}
// the behaviour is the same for every error code.
if (response.getCode() != 0) {
aTransaction.setErrorCode(resposne.getCode());
transactionService.saveOrUpdate(aTransaction);
throw AuthroizationError("Error on auth");
}
aTransaction.completed();
mainDocument.setAuthorizationCode(0);
mainDocument.authorize(); // will change state to "Authorized"
} catch (Exception e) {
mainDocument.authorize(); // will not change state because authorizationCode != 0 or its null.
} finally {
saveOrUpdate(mainDocument);
}
}
Когда происходит потеря обновления и как это влияет на систему:
- Идентификатор основного документа: 1@Thread-1 пытается авторизоваться
- Документ не авторизован, выполнение продолжается
- Проходит через веб-сервис и авторизуется ОК
< li>Транзакция закрывается и происходит фиксация. - Пока 1 выполняет фиксацию, приходит MainDocument 1@Thread-2 и пытается
аутентифицироваться. - 1 еще не сохраняется, Thread-2 пытается авторизоваться.
Поток-2 отклонен WS с ответом «документ 1 уже авторизован». - Поток-2 пытается зафиксировать.
- Поток-1 сначала фиксирует документ 1, Thread-2 фиксируется на втором месте.
Сложность возникает потому, что воспроизвести практически невозможно. Это происходит только в рабочей среде, и даже если я попытаюсь загрузить сервер сотнями вызовов, я не смогу добиться такого же поведения.
Реализованные решения:
- Потоковый барьер: если два потока с одинаковым идентификатором MainDocument пытаются авторизоваться, последний вошедший будет отклонен. Он реализован с аспектом с порядком 100, поэтому выполняется после фиксации @Transactional. Протестировано и проверено на трассировке стека, которую транзакция фиксирует до того, как аспект перехватывает и удаляет поток из барьера.
- @Version, которая работает в других разделах системы, вызывая исключение OptimisticLockException при попытке одной фиксации. чтобы переопределить другую фиксацию из более старой транзакции. В этом случае исключение OptimisticLockException не возникает.
- Транзакция сохраняется с помощью @Transactional(propagation = REQUIRES_NEW), поэтому она не зависит от основной транзакции и фиксируется правильно. С помощью этих транзакций становится ясно, что потерянное обновление является проблемой, поскольку мы видим завершенную транзакцию с сообщением об успехе, а MainDocument сохраняется с другим состоянием, без ошибок в файле server.log.
- Используя Imperva SecureSphere, мы можем проверять все обновления в определенной таблице. Мы можем ясно видеть, как первая транзакция фиксируется с правильным состоянием, а вторая транзакция перезаписывает первую.
Для ясности: в час поступает более 1000 запросов. и 99,99% этих запросов заканчиваются правильно. Общее количество случаев возникновения данной проблемы составляет около 20 в месяц.
Добавлено 13.09.17:
The saveOrUpdate метод, который мы используем, если необходимо:
* "http://blog.xebia.com/2009/03/23/jpa-im ... -entities/" >JPA
* implementation patterns: Saving (detached) entities
*
* @param entity
*/
protected E saveOrUpdate(E entity) {
if (entity.getId() == null) {
getJpaTemplate().persist(entity);
return entity;
}
if (!getJpaTemplate().getEntityManager().contains(entity)) {
return merge(entity);
}
return entity;
}
Подробнее здесь: https://stackoverflow.com/questions/461 ... ng-and-jpa