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();
    }
}