java - ejemplo - JPA: consultas de almacenamiento en caché
jpa java (6)
Estoy usando JPA para cargar y persistir entidades en mi aplicación web basada en Java EE. Hibernate se utiliza como una implementación de JPA, pero no utilizo funciones específicas de Hibernate y solo trabajo con JPA puro.
Aquí hay alguna clase DAO, note el método 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(); } }
El método es bastante simple pero tiene un gran inconveniente. Cada vez que se llama al método, las siguientes acciones se realizan en algún lugar dentro de la implementación de JPA:
- La expresión JPQL se analiza y se compila en SQL.
- Se crea e inicializa la instancia Statement o PreparedStatement.
- Instancia de instrucción se llena con parámetros y se ejecuta.
Creo que los pasos 1 y 2 de arriba deben implementarse una vez por vida de la aplicación. Pero, ¿cómo hacerlo? En otras palabras, necesito que las instancias de Query se guarden en caché.
Por supuesto que puedo implementar tal caché de mi lado. Pero espera, ¡estoy usando ORM potentes y modernos! ¿Ya no me hicieron esto?
Tenga en cuenta que no estoy mencionando algo como la caché de consultas de Hibernate que almacena en caché el resultado de las consultas. Aquí me gustaría ejecutar mis consultas un poco más rápido.
JPA 2.1, sección "3.1.1 Interfaz EntityManager":
Los objetos Query, TypedQuery, StoredProcedureQuery, CriteriaBuilder, Metamodel y EntityTransaction obtenidos de un administrador de entidades son válidos mientras ese administrador de entidades está abierto.
La lección para llevar a casa de esta cita es que los tipos de consultas alistados solo se pueden almacenar en caché mientras el administrador de entidades permanezca abierto, algo que no tenemos que decir sobre los administradores de entidades administradas por contenedores.
Tres soluciones vienen a la mente. 1) Consultas con nombre como otros han señalado. 2) Guarde en caché un CriteriaQuery
lugar y esperemos que el proveedor pueda arrojar algún tipo de optimizaciones. 3) Use un administrador de entidad administrado por la aplicación (que permanece abierto).
Guardar un 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 un administrador de entidad administrado por la aplicación
@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();
}
}
El es un caché de plan de consulta en Hibernate. Por lo tanto, el HQL no se analiza cada vez que se llama al DAO (por lo que el n. ° 1 aparece realmente solo una vez en la vida útil de su aplicación). Es QueryPlanCache . No está muy documentado, ya que "simplemente funciona". Pero puedes encontrar más información aquí .
Lo que quieres es un NamedQuery. En su entidad de Pedido usted pone:
@NamedQueries({
@NamedQuery( name = "getOrderByCustomerId" query = "SELECT o FROM Order o WHERE o.customerId = :customerId")
})
Luego, en su DAO use em.createNamedQuery ("getOrderByCustomerId") en lugar de volver a crear la consulta.
Use consultas con nombres definidos estáticamente. Son más eficientes porque el proveedor de persistencia JPA puede traducir la cadena JP QL a SQL una vez en el momento de inicio de la aplicación, a diferencia de cada vez que se ejecuta la consulta, y se recomienda en particular para las consultas que se ejecutan con frecuencia.
Una consulta con nombre se define utilizando la anotación @NamedQuery
que se utiliza normalmente en la clase de entidad del resultado. En su caso, en la entidad del 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 {
...
}
También se recomienda prefijar consultas nombradas con el nombre de la entidad (para tener algún tipo de espacio de nombre y evitar colisiones).
Y luego en el DAO:
class OrderDao {
EntityManager em;
List getOrders(Long customerId) {
return em.createNamedQuery("Order.findByCustomerId")
.setParameter("customerId", customerId);
.getResultList();
}
}
PD: reutilicé la consulta que sugirió como ejemplo, pero de alguna manera es extraño tener el customerId
de customerId
en el Order
; en cambio, esperaría un Customer
.
Referencias
- Especificación JPA 1.0
- Sección 3.6.4 "Consultas con nombre"
NamedQueries es el concepto que estás buscando.
No puede preparar consultas que no tienen nombre . Esa es la razón principal por la que debe intentar tener consultas con nombre en lugar de consultas simples dentro de su código. Además, las consultas con nombre se pueden almacenar en caché, mientras que las consultas simples dentro de su código java no pueden. Por supuesto, esta es una función opcional y está habilitada usando sugerencias sobre su consulta nombrada.