Если не задать ни одно из этих значений, то поставщик будет сам решать, какой механизм кэширования применять. В коде, приведенном в листинге 6.35, показано, как использовать соответствующий механизм кэширования. Сначала мы создаем объект Customer и обеспечиваем его постоянство. Поскольку сущность Customer является кэшируемой (см. листинг 6.34), она должна быть размещена в кэше второго уровня (с помощью метода EntityManagerFactory.getCache(). contains()). Вызов метода ache.evict(Customer.class) удаляет эту сущность из кэша.
Customer customer = new Customer("Патриция", "Джейн", "plecomte@mail.com");
tx.begin();
em.persist(customer);
tx.commit();
// Использует EntityManagerFactory для получения Cache
Cache cache = emf.getCache();
// Сущность Customer должна располагаться в кэше
assertTrue(cache.contains(Customer.class, customer.getId()));
// Удаляет сущность Customer из кэша
cache.evict(Customer.class);
// Сущность Customer больше не должна располагаться в кэше
assertFalse(cache.contains(Customer.class, customer.getId()));
Конкурентный доступ
JPA можно использовать для изменения постоянных данных, а JPQL — для извлечения данных, соответствующих определенным критериям. Все это может происходить в рамках приложения, выполняющегося в кластере с множеством узлов, множеством потоков и одной базой данных, благодаря чему осуществление конкурентного доступа к сущностям стало обычным явлением. Когда это имеет место, синхронизация должна контролироваться приложением с использованием механизма блокировки. Независимо от того, является приложение сложным или простым, есть вероятность, что вы решите применять блокировку где-нибудь в своем коде.
Чтобы проиллюстрировать проблему конкурентного доступа к базе данных, обратимся к примеру приложения с двумя конкурирующими потоками, показанными на рис. 6.4. Один поток ищет книгу по ее идентификатору и повышает цену книги на $2. Другой поток делает то же самое, но повышает цену книги на $5. Если вы выполните эти два потока одновременно в отдельных транзакциях, осуществляя манипуляции с одной и той же книгой, то не сможете предсказать окончательную цену книги. В этом примере начальная цена книги составляет $10. В зависимости от того, какая транзакция финиширует последней, цена может повыситься до $12 или 15.
Рис. 6.4. Транзакции № 1 (tx1) и № 2 (tx2), одновременно обновляющие цену книги
Эта проблема конкурентного доступа, при которой «победителем» становится транзакция, которая фиксируется последней, неспецифична для JPA. При работе с базами данных эту проблему приходится решать с давних пор, и были найдены разные решения, позволяющие изолировать одну транзакцию от другой. Распространенный механизм, задействуемый базами данных, — это блокировка строки, в отношении которой выполняется SQL-оператор.
JPA 2.1 использует два разных механизма блокировки (версия JPA 1.0 поддерживала только оптимистическую блокировку).
•
•
В качества примера из повседневной жизни, подкрепляющего эти концепции, представьте себе «оптимистический и пессимистический переходы через улицу». Там, где движение транспорта совсем неинтенсивно, вы сможете перейти через дорогу, не проверяя, нет ли приближающихся автомобилей. Но вы не сможете так поступить в центре города!
JPA использует разные механизмы блокировки на разных уровнях API-интерфейса. Применение как пессимистической, так и оптимистической блокировки возможно с помощью методов EntityManager.find и EntityManager.refresh (в дополнение к методу lock), а также благодаря JPQL-запросам. Иначе говоря, блокировка может быть обеспечена на уровне менеджера сущностей и на уровне Query с помощью методов, приведенных в табл. 6.5 и 6.6.