JPA: запросы кеширования
Я использую JPA для загрузки и сохранения объектов в своем веб-приложении на основе Java EE. Hibernate используется как реализация JPA, но я не использую Hibernate-специфические функции и работаю только с чистой JPA.
Вот несколько классов DAO, обратите внимание на метод getOrders
:
class OrderDao {
EntityManager em;
List getOrders(Long customerId) {
Query q = em.createQuery(
"SELECT o FROM Order o WHERE o.customerId = :customerId");
q.setParameter("customerId", customerId);
return q.getResultList();
}
}
Метод довольно прост, но имеет большой недостаток. Каждый раз, когда метод вызывается, следующие действия выполняются где-то в рамках реализации JPA:
- Выражение JPQL анализируется и компилируется в SQL.
- Создается и инициализируется экземпляр Statement или PreparedStatement.
- Экземпляр экземпляра заполняется параметрами и выполняется.
Я считаю, что шаги 1 и 2 выше должны быть реализованы один раз за время жизни приложения. Но как это сделать? Другими словами, мне нужно, чтобы экземпляры Query были кэшированы.
Конечно, я могу реализовать такой кеш на моей стороне. Но подождите, я использую современные мощные ORM! Разве они не сделали это для меня?
Обратите внимание, что я не упоминаю что-то вроде кэша запросов Hibernate, который кэширует результат запросов. Здесь я хотел бы выполнить мои запросы немного быстрее.
Ответы
Ответ 1
Это кеш плана запроса в Hibernate. Таким образом, HQL не анализируется каждый раз, когда вызывается DAO (так что # 1 действительно встречается только один раз в вашей жизни приложения). Это QueryPlanCache. Это не сильно документировано, поскольку оно "просто работает". Но здесь вы можете найти дополнительную информацию .
Ответ 2
Использовать статически определенные именованные запросы. Они более эффективны, потому что провайдер постоянства JPA может переводить строку JP QL в SQL один раз во время запуска приложения, а не каждый раз, когда выполняется запрос, и рекомендуется, в частности, для часто выполняемых запросов.
Именованный запрос определяется с помощью аннотации @NamedQuery
, которая обычно используется в классе сущности результата. В вашем случае объект Order
:
@Entity
@NamedQueries({
@NamedQuery(name="Order.findAll",
query="SELECT o FROM Order o"),
@NamedQuery(name="Order.findByPrimaryKey",
query="SELECT o FROM Order o WHERE o.id = :id"),
@NamedQuery(name="Order.findByCustomerId",
query="SELECT o FROM Order o WHERE o.customerId = :customerId")
})
public class Order implements Serializable {
...
}
Также рекомендуется префикс именованных запросов с именем сущности (иметь какое-то пространство имен и избегать коллизий).
И затем в DAO:
class OrderDao {
EntityManager em;
List getOrders(Long customerId) {
return em.createNamedQuery("Order.findByCustomerId")
.setParameter("customerId", customerId);
.getResultList();
}
}
PS: Я повторно использовал запрос, который вы предложили в качестве примера, но как-то странно иметь customerId
в Order
, я ожидал бы Customer
.
Ссылки
- Спецификация JPA 1.0
- Раздел 3.6.4 "Именованные запросы"
Ответ 3
NamedQueries - это концепция, которую вы ищете.
Ответ 4
То, что вы хотите, это NamedQuery. На вашем Заказе вы указали:
@NamedQueries({
@NamedQuery( name = "getOrderByCustomerId", query = "SELECT o FROM Order o WHERE o.customerId = :customerId")
})
Затем в своем DAO вместо создания запроса используйте em.createNamedQuery("getOrderByCustomerId").
Ответ 5
Невозможно подготовить запросы, не названные. Это основная причина, по которой вы должны пытаться иметь именованные запросы, а не простые запросы внутри вашего кода.
Кроме того, именованные запросы можно кэшировать, а простые запросы внутри вашего java-кода не могут. Конечно, это необязательная функция и включается с помощью подсказок по вашему именованному запросу.
Ответ 6
JPA 2.1, раздел "3.1.1 Интерфейс EntityManager":
Query, TypedQuery, StoredProcedureQuery, CriteriaBuilder, Объекты Metamodel и EntityTransaction, полученные от объекта менеджер действительны, пока этот диспетчер сущностей открыт.
Урок, взятый из этой цитаты, состоит в том, что типы заверенных запросов могут кэшироваться только до тех пор, пока диспетчер объектов остается открытым, о чем мы не говорим о менеджерах сущностей, управляемых контейнером.
На ум приходят три решения. 1) Именованные запросы, как указывали другие. 2) Вместо этого кэшируйте a CriteriaQuery
, и, надеюсь, провайдер может отказаться от каких-либо оптимизаций. 3) Используйте диспетчер объектов, управляемый приложениями (который остается открытым).
Cache a CriteriaQuery
@Stateless
public class OrderRepository
{
@PersistenceUnit
EntityManagerFactory emf;
@PersistenceContext
EntityManager em;
private CriteriaQuery<Order> query;
private Parameter<Long> param;
@PostConstruct
private void constructQuery() {
CriteriaBuilder b = emf.getCriteriaBuilder();
query = b.createQuery(Order.class);
param = b.parameter(long.class);
...
}
public List<Order> findByCustomerKey(long key) {
return em.createQuery(query)
.setParameter(param, key)
.getResultList();
}
}
Использовать диспетчер объектов, управляемый приложениями
@Stateless
public class OrderRepository
{
@PersistenceUnit
EntityManagerFactory emf;
private EntityManager em;
private TypedQuery<Order> query;
@PostConstruct
private void initialize() {
em = emf.createEntityManager();
query = em.createQuery("SELECT o FROM Order o WHERE o.id = ?1", Order.class);
}
public List<Order> findByCustomerKey(long key) {
try {
return query.setParameter(1, key)
.getResultList();
}
finally {
em.clear(); // returned entities are detached
}
}
@PreDestroy
private void closeEntityManager() {
em.close();
}
}