JPA:缓存查询

时间:2010-08-26 19:22:28

标签: java hibernate jpa caching

我正在使用JPA在基于Java EE的Web应用程序中加载和持久化实体。 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实现中的某个位置执行以下操作:

  1. 解析JPQL表达式并将其编译为SQL。
  2. 创建并初始化Statement或PreparedStatement实例。
  3. 语句实例填充参数并执行。

我认为上述第1步和第2步应该在每个应用程序生命周期内执行一次。但是怎么做呢?换句话说,我需要缓存Query实例。

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

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

6 个答案:

答案 0 :(得分:18)

使用静态定义的命名查询。它们更有效,因为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代替。{/ p>

参考

  • JPA 1.0规范
    • 第3.6.4节“命名查询”

答案 1 :(得分:5)

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

答案 2 :(得分:4)

NamedQueries是您正在寻找的概念。

答案 3 :(得分:2)

你想要的是一个NamedQuery。在您的订单实体上:

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

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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

JPA 2.1,section" 3.1.1 EntityManager Interface":

  

Query,TypedQuery,StoredProcedureQuery,CriteriaBuilder,   从实体获取的Metamodel和EntityTransaction对象   经理在实体经理开放时有效。

从这句话中带回家的教训是,只要实体经理保持开放,就只能缓存入伍的查询类型 - 我们对容器管理的实体经理一无所知。

我想到了三种解决方案。 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();
    }
}