2012-05-15 2 views
3

У меня есть эта сделка:состояние гонки, хотя использование транзакций

em.getTransaction().begin(); 
{ 
    final Payment payment = em.find(Payment.class, id); 
    if (payment.status != Status.INIT) 
     throw new IllegalStateException("Cannot set to PAID, is not INIT but " + status); 

    payment.status = Status.PAID; 

} 
em.getTransaction().commit(); 
log.info("Payment " + id + " was paid"); 

Однако, как вы можете видеть здесь, сделка не препятствует состояние гонки:

[11:10:18.265] INFO [PaymentServlet] [MSP] Status COMPLETED 
[11:10:18.265] INFO [PaymentServlet] Payment c76f9e75-99d7-4721-a8ac-e3a638dd8317 was paid 
[11:10:18.267] INFO [PaymentServlet] [MSP] Status COMPLETED 
[11:10:18.267] INFO [PaymentServlet] Payment c76f9e75-99d7-4721-a8ac-e3a638dd8317 was paid 

оплата устанавливается в PAID дважды. Мое исключение не выбрасывается, и нет откат или что-то еще.

Что я делаю неправильно?

+3

У вас есть поле @Version в платеже? – Anonymoose

+0

@Anonymoose Нет, у меня нет особых возможностей параллелизма в любом месте. Я думал, что для этого нужны транзакции. –

+0

Это ваша «специальная вещь параллелизма». – unbeli

ответ

7

Вам нужно использовать оптимистичную блокировку. Оптимистическая блокировка - это то, где конфликтующие обновления редки, поэтому допустимо откатывать случайные транзакции, когда это происходит. Пессимистическая блокировка заставляет базу данных удерживать блокировку объекта во время его использования, эффективно однопоточное все и потенциально вызывая проблемы с производительностью. См. http://en.wikibooks.org/wiki/Java_Persistence/Locking#JPA_2.0_Locking для более подробного объяснения.

Чтобы решить проблему здесь, вы должны добавить поле в Оплата (традиционное объявление является частной длинной версией) и дать ему JPA @Version аннотацию. Если вы управляете своей схемой вручную, убедитесь, что соответствующий столбец существует в правой таблице. Затем JPA будет использовать это поле, чтобы проверять конфликтующие обновления и откатывать транзакцию, если конфликт существует.

Обновление: Подробнее о пессимистической блокировке здесь: https://blogs.oracle.com/carolmcdonald/entry/jpa_2_0_concurrency_and Короче говоря, вы можете настроить JPA для блокировки объектов, но это крайне редко, что это хорошая идея, чтобы сделать это. Иными словами, если вы были ручным кодированием запросов к JDBC, вам нужно было бы написать «для обновления» в конце каждого выбора, чтобы вызвать пессимистическую блокировку; по умолчанию не следует блокировать чтение, потому что он заставляет базы данных и пользователей базы данных плакать.

+0

Однако, не следует ли автоматически применять пессимистическую (или действительно какую-либо) блокировку здесь? Сейчас транзакция не делает то, что я ожидаю от нее: поддержание целостности. Является ли мое понимание сделок неправильным? (Должен ли я обновить весь мой JPA-код сейчас? XD) –

+0

На практике. ACID очень дорого. Обычный уровень изоляции по умолчанию «повторяемый уровень чтения» обычно является хорошим компромиссом по той же причине, что оптимистическая блокировка редко приводит к откату, а именно, что конфликты встречаются редко, если не спровоцированы. Вы все равно получаете Atomicity (все или ничего), Consistency (вся БД не будет недействительной), а Durability (фиксация не исчезнет). В основном. Опять же, компромиссы. – Anonymoose

+0

Возможно, вы сбиваете ACID с одним из наиболее распространенных методов его достижения: строгая двухфазная блокировка (S2PL). Сериализуемая изоляция моментальных снимков имеет меньшее количество откатов при высокой конкуренции, чем оптимистичное управление параллелизмом (OCC) без блокировки и блокировок S2PL. – kgrittn

1

Вы не говорите, какую базу данных вы используете или какой уровень изоляции транзакций. Если вы используете транзакции SERIALIZABLE, которые соответствуют стандарту SQL, вы не увидите эту ошибку. PostgreSQL до 9.1, некоторые конфигурации MS SQL Server и все версии Oracle не дают ваших реальных сериализуемых транзакций при их запросе, поэтому в таких средах должны использоваться явные блокировки. Большинство продуктов баз данных по умолчанию составляют READ COMMITTED уровень изоляции транзакций, поэтому вам, вероятно, потребуется явно запросить транзакции SERIALIZABLE.

Полное раскрытие информации, я работал с Dan R.K. Порты MIT, чтобы добавить истинные сериализуемые транзакции к PostgreSQL версии 9.1, чтобы программное обеспечение Wisconsin Courts могло эффективно решать эти проблемы. Примеры различий см. В разделе this Wiki page.

+0

Это PostgreSQL 9.0.5, и я не устанавливаю явно уровень изоляции. –

+0

Затем вы должны использовать некоторую форму явного блокирования. Варианты включают оптимистичную блокировку, как описано в другом ответе, консультативную блокировку, явную блокировку таблиц или любой из ряда других решений, закодированных на уровне приложения. Или вы можете обновить до 9.1 и использовать транзакции SERIALIZABLE. Эта глава в документах PostgreSQL может помочь: http://www.postgresql.org/docs/9.1/interactive/mvcc.html – kgrittn

+0

Модернизация может быть возможной, я рассмотрю это. Кстати, что вы получаете, когда запрашиваете Serializable до 9.1, и почему он не бросает ошибку «Я действительно не поддерживаю эту»? –

Смежные вопросы