JPA: caching queries

前端 未结 6 1573
心在旅途
心在旅途 2021-02-07 08:13

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

相关标签:
6条回答
  • 2021-02-07 08:49

    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.

    0 讨论(0)
  • 2021-02-07 08:50

    NamedQueries is the concept you're looking for.

    0 讨论(0)
  • 2021-02-07 09:00

    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.

    0 讨论(0)
  • 2021-02-07 09:02

    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.

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

    @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:

    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 customerId on the Order, I would expect a Customer instead.

    References

    • JPA 1.0 Specification
      • Section 3.6.4 "Named Queries"
    0 讨论(0)
  • 2021-02-07 09:12

    JPA 2.1, section "3.1.1 EntityManager Interface":

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

    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 CriteriaQuery instead and hopefully the provider can toss in some kind of optimizations out of it. 3) Use an application-managed entity manager (that remains open).

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

    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();
        }
    }
    
    0 讨论(0)
  • 2021-02-07 09:13

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

    @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.

    0 讨论(0)
提交回复
热议问题