JPA: кеширование запросов - PullRequest
       7

JPA: кеширование запросов

15 голосов
/ 26 августа 2010

Я использую JPA для загрузки и сохранения сущностей в моем веб-приложении на основе Java EE. Hibernate используется в качестве реализации JPA, но я не использую специфичные для Hibernate функции и работаю только с чистым JPA.

Вот некоторый класс DAO, обратите внимание, getOrders метод:

class OrderDao {
  EntityManager em;

  List getOrders(Long customerId) {
    Query q = em.createQuery(
      "<i>SELECT o FROM Order o WHERE o.customerId = :customerId</i>");
    q.setParameter("<i>customerId</i>", customerId);
    <b>return</b> q.getResultList();
  }
}

Метод довольно прост, но имеет большой недостаток. Каждый раз, когда метод вызывается, в рамках реализации JPA выполняются следующие действия:

  1. JPQL-выражение анализируется и компилируется в SQL.
  2. Создается и инициализируется экземпляр Statement или PreparedStatement.
  3. Экземпляр оператора заполнен параметрами и выполнен.

Я считаю, что шаги 1 и 2 выше должны быть реализованы один раз за время жизни приложения. Но как это сделать? Другими словами, мне нужно, чтобы экземпляры Query были кэшированы.

Конечно, я могу реализовать такой кеш на своей стороне. Но подождите, я использую современные мощные ORM! Разве они уже не сделали это для меня?

Обратите внимание, что я не упоминаю нечто вроде кэша запросов Hibernate, который кэширует результаты запросов. Здесь я бы хотел выполнить свои запросы немного быстрее.

Ответы [ 6 ]

18 голосов
/ 26 августа 2010

Использовать статически определенные именованные запросы .Они более эффективны, поскольку поставщик сохраняемости 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 «Именованные запросы»
5 голосов
/ 26 августа 2010

Это кеш плана запросов в Hibernate. Таким образом, HQL не анализируется каждый раз, когда вызывается DAO (поэтому # 1 действительно происходит только один раз за время жизни вашего приложения). Это QueryPlanCache . Это не сильно документировано, так как «просто работает». Но вы можете найти больше информации здесь .

4 голосов
/ 26 августа 2010

NamedQueries - это концепция, которую вы ищете.

2 голосов
/ 26 августа 2010

То, что вы хотите, это NamedQuery. На вашем Заказе вы указали:

@NamedQueries({
    @NamedQuery( name = "getOrderByCustomerId", query = "SELECT o FROM Order o WHERE o.customerId = :customerId")
})

Затем в вашем DAO вместо создания запроса используйте em.createNamedQuery ("getOrderByCustomerId").

1 голос
/ 15 июня 2017

JPA 2.1, раздел «3.1.1 Интерфейс EntityManager»:

Объекты Query, TypedQuery, StoredProcedureQuery, CriteriaBuilder, Metamodel и EntityTransaction, полученные от менеджера сущностей, действительны, пока этот менеджер сущностейopen.

Урок, который следует извлечь из этой цитаты, заключается в том, что перечисленные типы запросов могут кэшироваться только до тех пор, пока менеджер сущностей остается открытым - о чем мы не говорим для сущности, управляемой контейнером.менеджеры.

На ум приходят три решения.1) Именованные запросы, как указали другие.2) Вместо этого кешируйте CriteriaQuery, и, надеюсь, провайдер сможет внести в него какие-то оптимизации.3) Используйте диспетчер объектов, управляемый приложением (который остается открытым).

Кэшируйте запрос 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();
    }
}
1 голос
/ 09 сентября 2012

Вы не можете подготовить запросы без имени .Это основная причина, по которой вы должны пытаться использовать именованные запросы, а не простые запросы внутри вашего кода.Кроме того, именованные запросы могут кэшироваться, в то время как простые запросы внутри вашего Java-кода не могут.Конечно, это необязательная функция, и она включается с помощью подсказок в вашем именованном запросе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...