Java JPA:缓存查询

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3578711/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 02:05:16  来源:igfitidea点击:

JPA: caching queries

javahibernatejpacaching

提问by Andrey

I'm using JPA to load and persist entities in my Java EE-based web application. Hibernate is used as an implementation of JPA, but I don't use Hibernate-specific features and only work with pure JPA.

我正在使用 JPA 在基于 Java EE 的 Web 应用程序中加载和保留实体。Hibernate 被用作 JPA 的实现,但我不使用 Hibernate 特定的功能,只使用纯 JPA。

Here is some DAO class, notice getOrdersmethod:

这是一些 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();
  }
}

Method is pretty simple but it has a big drawback. Each time the method is called following actions are performed somewhere within JPA implementation:

方法很简单,但它有一个很大的缺点。每次调用该方法时,都会在 JPA 实现中的某处执行以下操作:

  1. JPQL expression is parsed and compiled to SQL.
  2. Either Statement or PreparedStatement instance is created and initialized.
  3. Statement instance is filled with parameters and executed.
  1. JPQL 表达式被解析并编译为 SQL。
  2. 创建并初始化 Statement 或 PreparedStatement 实例。
  3. 语句实例填充参数并执行。

I believe that steps 1 and 2 of above should be implemented once per application lifetime. But how to make it? In other words, I need that Query instances to be cached.

我相信上面的第 1 步和第 2 步应该在每个应用程序生命周期内实现一次。但是如何实现呢?换句话说,我需要缓存 Query 实例。

Of course I can implement such a cache on my side. But wait, I am using modern powerful ORM's! Didn't they already made this for me?

当然,我可以在我这边实现这样的缓存。但是等等,我正在使用现代强大的 ORM!他们不是已经给我做了这个吗?

Notice that I'm not mentioning something like Hibernate query cache which caches result of queries. Here I'd like to execute my queries a bit more quickly.

请注意,我没有提到像 Hibernate 查询缓存这样缓存查询结果的东西。在这里,我想更快地执行我的查询。

采纳答案by Thierry-Dimitri Roy

The is a query plan cache in Hibernate. So the HQL is not parsed every time the DAO is called (so #1 really occurs only once in your application life-time). It's QueryPlanCache. It's not heavily documented, as it "just works". But you can find more info here.

这是 Hibernate 中的查询计划缓存。因此,每次调用 DAO 时都不会解析 HQL(因此 #1 在您的应用程序生命周期中实际上只出现一次)。它是QueryPlanCache。它没有大量记录,因为它“正常工作”。但您可以在此处找到更多信息。

回答by Affe

What you want is a NamedQuery. On your Order entity you put:

你想要的是一个 NamedQuery。在您的 Order 实体上,您输入:

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

Then in your DAO use em.createNamedQuery("getOrderByCustomerId") instead of recreating the query.

然后在您的 DAO 中使用 em.createNamedQuery("getOrderByCustomerId") 而不是重新创建查询。

回答by Chris Lercher

NamedQueriesis the concept you're looking for.

NamedQueries是您正在寻找的概念。

回答by Pascal Thivent

Use statically defined named queries. They are more efficient because the JPA persistence provider can translate the JP QL string to SQL once at application startup time, as opposed to every time the query is executed, and are recommended in particular for queries that are executed frequently.

使用静态定义的命名查询。它们更有效,因为 JPA 持久性提供程序可以在应用程序启动时将 JP QL 字符串转换为 SQL,而不是每次执行查询时,并且特别推荐用于频繁执行的查询。

A named query is defined using the @NamedQueryannotation that is typically used on the entity class of the result. In your case, on the Orderentity:

命名查询是使用@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 {
    ...
}

It is also recommended to prefix named queries with the entity name (to have some kind of name space and avoid collisions).

还建议使用实体名称作为命名查询的前缀(以获得某种名称空间并避免冲突)。

And then in the DAO:

然后在 DAO 中:

class OrderDao {
    EntityManager em;

    List getOrders(Long customerId) {
        return em.createNamedQuery("Order.findByCustomerId")
                 .setParameter("customerId", customerId);
                 .getResultList();
    }
}

PS: I reused the query you suggested as example but it's somehow weird to have the customerIdon the Order, I would expect a Customerinstead.

PS:我重用了您建议的查询作为示例,但是将customerId放在 上有点奇怪Order,我希望Customer改为使用。

References

参考

  • JPA 1.0 Specification
    • Section 3.6.4 "Named Queries"
  • JPA 1.0 规范
    • 第 3.6.4 节“命名查询”

回答by ichalos

You can't prepare queries that are not named. That is the main reason you should try to have named queries rather than simple queries inside your code. Also, named queries can be cached while simple queries inside your java code cannot. Of course this is an optional feature and is enabled using hints on your named query.

您无法准备未命名的查询。这是您应该尝试在代码中使用命名查询而不是简单查询的主要原因。此外,命名查询可以缓存,而 Java 代码中的简单查询则不能。当然,这是一项可选功能,可以使用命名查询的提示启用。

回答by Martin Andersson

JPA 2.1, section "3.1.1 EntityManager Interface":

JPA 2.1,“3.1.1 EntityManager 接口”部分:

The Query, TypedQuery, StoredProcedureQuery, CriteriaBuilder, Metamodel, and EntityTransaction objects obtained from an entity manager are valid while that entity manager is open.

从实体管理器获取的 Query、TypedQuery、StoredProcedureQuery、CriteriaBuilder、Metamodel 和 EntityTransaction 对象在实体管理器打开时有效。

The lesson to take home from this quote is that the enlisted query types can only be cached for as long as the entity manager remains open - which we have no saying about for container-managed entity managers.

从这句话中得到的教训是,只要实体管理器保持打开状态,就可以缓存登记的查询类型——对于容器管理的实体管理器,我们没有说。

Three solutions come to mind. 1) Named queries as others have pointed out. 2) Cache a CriteriaQueryinstead and hopefully the provider can toss in some kind of optimizations out of it. 3) Use an application-managed entity manager (that remains open).

想到了三个解决方案。1) 其他人指出的命名查询。2) 缓存 aCriteriaQuery并且希望提供者可以对其进行某种优化。3) 使用应用程序管理的实体管理器(保持打开状态)。

Cache a CriteriaQuery

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

Use an application-managed entity manager

使用应用程序管理的实体管理器

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