JPA Пессимистическая попытка блокировки никогда не заканчивается
Я пытаюсь использовать пессимистическую блокировку в JPA, а не Hibernate 3 для базы данных Postgres. Я не могу получить тайм-аут блокировки - кажется, он висит вечно.
Вот пример:
EntityManagerFactory factory;
// (initialise the factory )
EntityManager em1 = factory.createEntityManager();
EntityManager em2 = factory.createEntityManager();
// em1 gets a lock
EntityTransaction transaction1 = em1.getTransaction();
transaction1.begin();
MyObject object1 = em1.find( MyObject.class, 1, LockModeType.PESSIMISTIC_READ );
// em2 tries for a lock
Map<String,Object> timeoutProperties = new HashMap<String,Object>();
timeoutProperties.put("javax.persistence.lock.timeout", 5000);
EntityTransaction transaction2 = em2.getTransaction();
transaction2.begin();
MyObject object2 = em2.find( MyObject.class, 1, LockModeType.PESSIMISTIC_READ, timeoutProperties );
// After five seconds I expect em2 to bail out, but it never does.
transaction1.rollback();
transaction2.rollback();
Насколько я понимаю, em2 должен был пытаться заблокировать до пяти секунд (5000 мс), а затем должен был выдать исключение. Вместо этого код становится заблокированным.
Если я запускаю это в двух разных потоках, я вижу, что thread2 (с em2) получает блокировку, как только thread1 (em1) снимает ее. Так что блокировка происходит, просто никогда не выходит из строя.
Я получаю тот же эффект с PESSIMISTIC_WRITE, и с любым значением времени ожидания (2 мс, 0 мс "NO WAIT") и т. Д.
Я использую Hibernate 3.6.10 Final (последняя версия Hibernate 3) и Postgres jdbc driver 9.2-1003.jdbc4 (последняя версия драйвера). Я работаю с базой данных Postgres 8.4.
Вся документация, которую я нашел, предполагает, что это должно работать. Есть идеи?
спасибо, Аластер
2 ответа
Postgres SELECT для обновления синтаксиса предоставляет только опции, чтобы не ждать, если блокировка не может быть получена сразу. Смотрите postgres документы.
Чтобы операция не ожидала принятия других транзакций, используйте параметр NOWAIT. С NOWAIT оператор сообщает об ошибке, а не об ожидании, если выбранная строка не может быть заблокирована немедленно. Обратите внимание, что NOWAIT применяется только к блокировкам на уровне строк - требуемая блокировка на уровне таблиц ROW SHARE по-прежнему выполняется обычным способом (см. Главу 13). Сначала вы можете использовать LOCK с опцией NOWAIT, если вам нужно получить блокировку на уровне таблицы без ожидания.
При работе с postgres я заметил, что любое значение, превышающее 0 для тайм-аута, вызовет проблему спящего режима SELECT FOR UPDATE
но когда тайм-аут равен 0, он выдаст SELECT FOR UPDATE NO WAIT
Поместите это в ваш файл persistence.xml:
<property name="javax.persistence.lock.timeout" value="0"/>
Или установите свойство перед вызовом первой блокировки.
Javax .persistence.lock.timeout не будет работать для меня, если он предоставлен, как показано ниже
@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout",value = "15000")})
Но потом я попробовал кое-что еще, что сработало. Вместо использования @Repository и CrudRepository теперь я настраиваю свой hbernate с помощью диспетчера сущностей. Используется createQuery вместе с блокировкой и установкой тайм-аута блокировки. И эта конфигурация работает должным образом. У меня есть две транзакции, запущенные в parellel и пытающиеся заблокировать одну и ту же строку в БД. Первая транзакция может получить блокировку WRITE и удерживает блокировку около 10 секунд перед снятием блокировки. Между тем, вторая транзакция пытается получить блокировку той же строки, но, поскольку для javax.persistence.lock.timeout установлено значение 15 секунд, она ожидает снятия блокировки, а затем получает собственную блокировку. Следовательно, делая поток сериализованным.
@Component
public class Repository {
@PersistenceContext
private EntityManager em;
public Optional<Cache> getById(int id){
List<Cache> list = em.createQuery("select c from Cache c where c.id = ?1")
.setParameter(1, id)
.setHint("javax.persistence.lock.timeout", 15000)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.getResultList();
return Optional.ofNullable(list.get(0));
}
public void save(Cache cache) {
cache = em.find(Cache.class, cache.getId());
em.merge(cache);
}
}